Cost: rework param that says if it's by effect or by Spell/ActivatedAbility cost

This commit is contained in:
Hans Mackowiak
2021-12-12 05:36:39 +00:00
committed by Michael Kamensky
parent c18f7221c6
commit aa6f2e3b6c
122 changed files with 720 additions and 912 deletions

View File

@@ -131,7 +131,7 @@ public class AiAttackController {
}
for (SpellAbility sa : c.getSpellAbilities()) {
if (sa.getApi() == ApiType.Animate) {
if (ComputerUtilCost.canPayCost(sa, defender)
if (ComputerUtilCost.canPayCost(sa, defender, false)
&& sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) {
Card animatedCopy = AnimateAi.becomeAnimated(c, sa);
defenders.add(animatedCopy);
@@ -1159,8 +1159,8 @@ public class AiAttackController {
// Check if the card actually has an ability the AI can and wants to play, if not, attacking is fine!
for (SpellAbility sa : attacker.getSpellAbilities()) {
// Do not attack if we can afford using the ability.
if (sa.isAbility()) {
if (ComputerUtilCost.canPayCost(sa, ai)) {
if (sa.isActivatedAbility()) {
if (ComputerUtilCost.canPayCost(sa, ai, false)) {
return false;
}
// TODO Eventually The Ai will need to learn to predict if they have any use for the ability before next untap or not.

View File

@@ -276,7 +276,7 @@ public class AiController {
}
rightapi = true;
if (!(exSA instanceof AbilitySub)) {
if (!ComputerUtilCost.canPayCost(exSA, player)) {
if (!ComputerUtilCost.canPayCost(exSA, player, true)) {
return false;
}
}
@@ -648,7 +648,7 @@ public class AiController {
// Ideally this should cast canPlaySa to determine that the AI is truly able/willing to cast a spell,
// but that is currently difficult to implement due to various side effects leading to stack overflow.
Card host = sa.getHostCard();
if (!ComputerUtil.castPermanentInMain1(player, sa) && host != null && !host.isLand() && ComputerUtilCost.canPayCost(sa, player)) {
if (!ComputerUtil.castPermanentInMain1(player, sa) && host != null && !host.isLand() && ComputerUtilCost.canPayCost(sa, player, false)) {
if (sa instanceof SpellPermanent) {
return sa;
}
@@ -744,7 +744,7 @@ public class AiController {
int oldCMC = -1;
boolean xCost = sa.costHasX() || sa.getHostCard().hasStartOfKeyword("Strive");
if (!xCost) {
if (!ComputerUtilCost.canPayCost(sa, player)) {
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
// for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check
// when the AI won't even be able to play the spell in the first place (even if it could afford it)
return AiPlayDecision.CantAfford;
@@ -782,7 +782,7 @@ public class AiController {
Cost wardCost = ComputerUtilCard.getTotalWardCost(tgt);
if (wardCost.hasManaCost()) {
amount = wardCost.getTotalMana().getCMC();
if (amount > 0 && !ComputerUtilCost.canPayCost(sa, player)) {
if (amount > 0 && !ComputerUtilCost.canPayCost(sa, player, true)) {
return AiPlayDecision.CantAfford;
}
}
@@ -808,7 +808,7 @@ public class AiController {
}
}
if (xCost && !ComputerUtilCost.canPayCost(sa, player)) {
if (xCost && !ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
// for dependent costs with X, e.g. Repeal, which require a valid target to be specified before a decision can be made
// on whether the cost can be paid, this can only be checked late after canPlaySa has been run (or the AI will misplay)
return AiPlayDecision.CantAfford;
@@ -871,7 +871,7 @@ public class AiController {
if (mana != null) {
if (mana.countX() > 0) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, player);
final int xPay = ComputerUtilCost.getMaxXValue(sa, player, sa.isTrigger());
if (xPay <= 0) {
return AiPlayDecision.CantAffordX;
}
@@ -2310,11 +2310,11 @@ public class AiController {
return null;
}
public CardCollectionView chooseSacrificeType(String type, SpellAbility ability, int amount, final CardCollectionView exclude) {
public CardCollectionView chooseSacrificeType(String type, SpellAbility ability, boolean effect, int amount, final CardCollectionView exclude) {
if (simPicker != null) {
return simPicker.chooseSacrificeType(type, ability, amount, exclude);
return simPicker.chooseSacrificeType(type, ability, effect, amount, exclude);
}
return ComputerUtil.chooseSacrificeType(player, type, ability, ability.getTargetCard(), amount, exclude);
return ComputerUtil.chooseSacrificeType(player, type, ability, ability.getTargetCard(), effect, amount, exclude);
}
private boolean checkAiSpecificRestrictions(final SpellAbility sa) {

View File

@@ -16,7 +16,6 @@ import com.google.common.collect.Lists;
import forge.card.CardType;
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
@@ -40,8 +39,8 @@ public class AiCostDecision extends CostDecisionMakerBase {
private final CardCollection discarded;
private final CardCollection tapped;
public AiCostDecision(Player ai0, SpellAbility sa) {
super(ai0);
public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect) {
super(ai0, effect);
ability = sa;
source = ability.getHostCard();
@@ -51,11 +50,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostAddMana cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
return PaymentDecision.number(c);
}
@@ -93,10 +88,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
if (type.contains("WithSameName")) {
return null;
}
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
if (type.equals("Random")) {
CardCollectionView randomSubset = CardLists.getRandomSubList(new CardCollection(hand), c);
@@ -134,25 +126,17 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostDamage cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostDraw cost) {
if (!cost.canPay(ability, player)) {
if (!cost.canPay(ability, player, isEffect())) {
return null;
}
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
List<Player> res = cost.getPotentialPlayers(player, ability);
@@ -174,10 +158,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
return null;
}
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
if (cost.getFrom().equals(ZoneType.Library)) {
return PaymentDecision.card(player.getCardsIn(ZoneType.Library, c));
@@ -193,10 +174,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostExileFromStack cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
List<SpellAbility> chosen = Lists.newArrayList();
for (SpellAbilityStackInstance si :source.getGame().getStack()) {
SpellAbility sp = si.getSpellAbility(true).getRootAbility();
@@ -209,12 +186,9 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostExiledMoveToGrave cost) {
Integer c = cost.convertAmount();
CardCollection chosen = new CardCollection();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
CardCollection typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Exile), cost.getType().split(";"), player, source, ability);
@@ -238,10 +212,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
return PaymentDecision.card(source);
}
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
final CardCollection typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source, ability);
@@ -260,19 +231,13 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostFlipCoin cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
return PaymentDecision.number(c);
}
@Override
public PaymentDecision visit(CostRollDice cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
return PaymentDecision.number(c);
}
@@ -282,10 +247,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
return PaymentDecision.card(source);
}
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
final CardCollection typeList = CardLists.getValidCards(player.getGame().getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source, ability);
@@ -307,7 +269,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
public PaymentDecision visit(CostGainLife cost) {
final List<Player> oppsThatCanGainLife = Lists.newArrayList();
for (final Player opp : cost.getPotentialTargets(player, source)) {
for (final Player opp : cost.getPotentialTargets(player, ability)) {
if (opp.canGainLife()) {
oppsThatCanGainLife.add(opp);
}
@@ -323,10 +285,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostMill cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
CardCollectionView topLib = player.getCardsIn(ZoneType.Library, c);
return topLib.size() < c ? null : PaymentDecision.number(c);
@@ -339,12 +298,8 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostPayLife cost) {
Integer c = cost.convertAmount();
if (c == null) {
// Generalize cost
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
if (!player.canPayLife(c)) {
int c = cost.getAbilityAmount(ability);
if (!player.canPayLife(c, isEffect())) {
return null;
}
// activator.payLife(c, null);
@@ -353,10 +308,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostPayEnergy cost) {
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
if (!player.canPayEnergy(c)) {
return null;
}
@@ -368,7 +320,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
if (cost.payCostFromSource()) {
return PaymentDecision.card(source);
}
Integer c = cost.convertAmount();
final Game game = player.getGame();
CardCollection chosen = new CardCollection();
CardCollectionView list;
@@ -379,9 +330,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
list = new CardCollection(player.getCardsIn(cost.getFrom()));
}
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
list = CardLists.getValidCards(list, cost.getType().split(";"), player, source, ability);
@@ -430,17 +379,12 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostTapType cost) {
final String amount = cost.getAmount();
Integer c = cost.convertAmount();
String type = cost.getType();
boolean isVehicle = type.contains("+withTotalPowerGE");
CardCollection exclude = new CardCollection();
exclude.addAll(tapped);
if (c == null && !isVehicle) {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
if (type.contains("sharesCreatureTypeWith")) {
return null;
}
@@ -470,6 +414,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
type = TextUtil.fastReplace(type, "+withTotalPowerGE", "");
totap = ComputerUtil.chooseTapTypeAccumulatePower(player, type, ability, !cost.canTapSource, Integer.parseInt(totalP), exclude);
} else {
int c = cost.getAbilityAmount(ability);
totap = ComputerUtil.chooseTapType(player, type, source, !cost.canTapSource, c, exclude, ability);
}
@@ -494,14 +439,10 @@ public class AiCostDecision extends CostDecisionMakerBase {
return null;
}
final String amount = cost.getAmount();
Integer c = cost.convertAmount();
int c = cost.getAbilityAmount(ability);
if (c == null) {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
CardCollectionView list = aic.chooseSacrificeType(cost.getType(), ability, c, null);
CardCollectionView list = aic.chooseSacrificeType(cost.getType(), ability, isEffect(), c, null);
return PaymentDecision.card(list);
}
@@ -510,10 +451,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
if (cost.payCostFromSource())
return PaymentDecision.card(source);
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
CardCollectionView res = ComputerUtil.chooseReturnType(player, cost.getType(), source, ability.getTargetCard(), c, ability);
return res.isEmpty() ? null : PaymentDecision.card(res);
@@ -544,10 +482,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
return PaymentDecision.card(getBestCreatureAI(hand));
}
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
int c = cost.getAbilityAmount(ability);
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability));
@@ -580,8 +515,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostRemoveAnyCounter cost) {
final String amount = cost.getAmount();
final int c = AbilityUtils.calculateAmount(source, amount, ability);
final int c = cost.getAbilityAmount(ability);
final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source);
if (c <= 0) {
@@ -785,10 +719,10 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostRemoveCounter cost) {
final String amount = cost.getAmount();
Integer c = cost.convertAmount();
final String type = cost.getType();
if (c == null) {
int c;
final String sVar = ability.getSVar(amount);
if (amount.equals("All")) {
c = source.getCounters(cost.counter);
@@ -802,8 +736,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
}
}
} else {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
c = cost.getAbilityAmount(ability);
}
if (!cost.payCostFromSource()) {
@@ -831,11 +764,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public PaymentDecision visit(CostUntapType cost) {
final String amount = cost.getAmount();
Integer c = cost.convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
int c = cost.getAbilityAmount(ability);
CardCollectionView list = ComputerUtil.chooseUntapType(player, cost.getType(), source, cost.canUntapSource, c, ability);

View File

@@ -149,13 +149,13 @@ public class ComputerUtil {
// TODO: update mana color conversion for Daxos of Meletis
if (cost == null) {
if (ComputerUtilMana.payManaCost(ai, sa)) {
if (ComputerUtilMana.payManaCost(ai, sa, false)) {
game.getStack().addAndUnfreeze(sa);
return true;
}
} else {
final CostPayment pay = new CostPayment(cost, sa);
if (pay.payComputerCosts(new AiCostDecision(ai, sa))) {
if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) {
game.getStack().addAndUnfreeze(sa);
if (sa.getSplicedCards() != null && !sa.getSplicedCards().isEmpty()) {
game.getAction().reveal(sa.getSplicedCards(), ai, true, "Computer reveals spliced cards from ");
@@ -202,7 +202,7 @@ public class ComputerUtil {
}
// Abilities before Spells (card advantage)
if (sa.isAbility()) {
if (sa.isActivatedAbility()) {
restrict += 40;
}
@@ -245,7 +245,7 @@ public class ComputerUtil {
// this is used for AI's counterspells
public static final boolean playStack(SpellAbility sa, final Player ai, final Game game) {
sa.setActivatingPlayer(ai);
if (!ComputerUtilCost.canPayCost(sa, ai))
if (!ComputerUtilCost.canPayCost(sa, ai, false))
return false;
final Card source = sa.getHostCard();
@@ -257,11 +257,11 @@ public class ComputerUtil {
final Cost cost = sa.getPayCosts();
if (cost == null) {
ComputerUtilMana.payManaCost(ai, sa);
ComputerUtilMana.payManaCost(ai, sa, false);
game.getStack().add(sa);
} else {
final CostPayment pay = new CostPayment(cost, sa);
if (pay.payComputerCosts(new AiCostDecision(ai, sa))) {
if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) {
game.getStack().add(sa);
}
}
@@ -284,7 +284,7 @@ public class ComputerUtil {
SpellAbility newSA = sa.copyWithNoManaCost();
newSA.setActivatingPlayer(ai);
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0)) {
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA) || !ComputerUtilMana.canPayManaCost(newSA, ai, 0, false)) {
return false;
}
@@ -302,16 +302,16 @@ public class ComputerUtil {
}
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
pay.payComputerCosts(new AiCostDecision(ai, newSA));
pay.payComputerCosts(new AiCostDecision(ai, newSA, false));
game.getStack().add(newSA);
return true;
}
public static final void playNoStack(final Player ai, SpellAbility sa, final Game game) {
public static final void playNoStack(final Player ai, SpellAbility sa, final Game game, final boolean effect) {
sa.setActivatingPlayer(ai);
// TODO: We should really restrict what doesn't use the Stack
if (ComputerUtilCost.canPayCost(sa, ai)) {
if (ComputerUtilCost.canPayCost(sa, ai, effect)) {
final Card source = sa.getHostCard();
if (sa.isSpell() && !source.isCopiedSpell()) {
sa.setHostCard(game.getAction().moveToStack(source, sa));
@@ -321,10 +321,10 @@ public class ComputerUtil {
final Cost cost = sa.getPayCosts();
if (cost == null) {
ComputerUtilMana.payManaCost(ai, sa);
ComputerUtilMana.payManaCost(ai, sa, effect);
} else {
final CostPayment pay = new CostPayment(cost, sa);
pay.payComputerCosts(new AiCostDecision(ai, sa));
pay.payComputerCosts(new AiCostDecision(ai, sa, effect));
}
AbilityUtils.resolve(sa);
@@ -564,7 +564,7 @@ public class ComputerUtil {
return -1;
}
public static CardCollection chooseSacrificeType(final Player ai, final String type, final SpellAbility ability, final Card target, final int amount, final CardCollectionView exclude) {
public static CardCollection chooseSacrificeType(final Player ai, final String type, final SpellAbility ability, final Card target, final boolean effect, final int amount, final CardCollectionView exclude) {
final Card source = ability.getHostCard();
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, ability);
@@ -572,7 +572,7 @@ public class ComputerUtil {
typeList.removeAll(exclude);
}
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability, effect));
// don't sacrifice the card we're pumping
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, ability, ai);
@@ -927,11 +927,11 @@ public class ComputerUtil {
// This try/catch should fix the "computer is thinking" bug
try {
if (!sa.isAbility() || sa.getApi() != ApiType.Regenerate) {
if (!sa.isActivatedAbility() || sa.getApi() != ApiType.Regenerate) {
continue; // Not a Regenerate ability
}
sa.setActivatingPlayer(controller);
if (!(sa.canPlay() && ComputerUtilCost.canPayCost(sa, controller))) {
if (!(sa.canPlay() && ComputerUtilCost.canPayCost(sa, controller, false))) {
continue; // Can't play ability
}
@@ -983,12 +983,12 @@ public class ComputerUtil {
// if SA is from AF_Counter don't add to getPlayable
// This try/catch should fix the "computer is thinking" bug
try {
if (sa.getApi() == null || !sa.isAbility()) {
if (sa.getApi() == null || !sa.isActivatedAbility()) {
continue;
}
if (sa.getApi() == ApiType.PreventDamage && sa.canPlay()
&& ComputerUtilCost.canPayCost(sa, controller)) {
&& ComputerUtilCost.canPayCost(sa, controller, false)) {
if (AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa).contains(card)) {
prevented += AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa);
}
@@ -1472,7 +1472,7 @@ public class ComputerUtil {
AiCardMemory.rememberCard(ai, c, AiCardMemory.MemorySet.MARKED_TO_AVOID_REENTRY);
}
if (!ComputerUtilCost.canPayCost(sa, ai)) {
if (!ComputerUtilCost.canPayCost(sa, ai, false)) {
continue;
}
return true;
@@ -1504,7 +1504,7 @@ public class ComputerUtil {
if (!sa.canTarget(enemy)) {
continue;
}
if (!ComputerUtilCost.canPayCost(sa, ai)) {
if (!ComputerUtilCost.canPayCost(sa, ai, false)) {
continue;
}
if (!GameActionUtil.getOptionalCostValues(sa).isEmpty()) {
@@ -2918,7 +2918,7 @@ public class ComputerUtil {
// only API-based SAs are supported, other things may lead to a NPE (e.g. Ancestral Vision Suspend SA)
continue;
} else if (ab.getApi() == ApiType.Mana && "ManaRitual".equals(ab.getParam("AILogic"))) {
// Mana Ritual cards are too complex for the AI to consider casting through a spell effect and will
// TODO Mana Ritual cards are too complex for the AI to consider casting through a spell effect and will
// lead to a stack overflow. Consider improving.
continue;
}
@@ -2926,7 +2926,7 @@ public class ComputerUtil {
// at this point, we're assuming that card will be castable from whichever zone it's in by the AI player.
abTest.setActivatingPlayer(ai);
abTest.getRestrictions().setZone(c.getZone().getZoneType());
if (AiPlayDecision.WillPlay == aic.canPlaySa(abTest) && ComputerUtilCost.canPayCost(abTest, ai)) {
if (AiPlayDecision.WillPlay == aic.canPlaySa(abTest) && ComputerUtilCost.canPayCost(abTest, ai, false)) {
targets.add(c);
}
}

View File

@@ -625,7 +625,7 @@ public class ComputerUtilCard {
if (sa.getApi() != ApiType.Destroy) {
continue;
}
if (!ComputerUtilCost.canPayCost(sa, opp)) {
if (!ComputerUtilCost.canPayCost(sa, opp, sa.isTrigger())) {
continue;
}
sa.setActivatingPlayer(opp);
@@ -1398,7 +1398,7 @@ public class ComputerUtilCard {
for (SpellAbility ab : c.getSpellAbilities()) {
Cost abCost = ab.getPayCosts();
if (abCost != null && abCost.hasTapCost()
&& (!abCost.hasManaCost() || ComputerUtilMana.canPayManaCost(ab, ai, 0))) {
&& (!abCost.hasManaCost() || ComputerUtilMana.canPayManaCost(ab, ai, 0, false))) {
nonCombatChance += 0.5f;
break;
}

View File

@@ -1020,7 +1020,7 @@ public class ComputerUtilCombat {
continue;
}
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
if (pBonus > 0) {
power += pBonus;
@@ -1039,7 +1039,7 @@ public class ComputerUtilCombat {
continue;
}
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
if (pBonus > 0) {
power += pBonus;
@@ -1155,7 +1155,7 @@ public class ComputerUtilCombat {
continue;
}
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
if (tBonus > 0) {
toughness += tBonus;
@@ -1174,7 +1174,7 @@ public class ComputerUtilCombat {
continue;
}
if (ComputerUtilCost.canPayCost(ability, blocker.getController())) {
if (ComputerUtilCost.canPayCost(ability, blocker.getController(), false)) {
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
if (tBonus > 0) {
toughness += tBonus;
@@ -1352,7 +1352,7 @@ public class ComputerUtilCombat {
continue;
}
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumAtt"), ability);
if (pBonus > 0) {
power += pBonus;
@@ -1371,7 +1371,7 @@ public class ComputerUtilCombat {
continue;
}
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
int pBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
if (pBonus > 0) {
power += pBonus;
@@ -1570,7 +1570,7 @@ public class ComputerUtilCombat {
if (ability.getPayCosts().hasTapCost() && !attacker.hasKeyword(Keyword.VIGILANCE)) {
continue;
}
if (!ComputerUtilCost.canPayCost(ability, attacker.getController())) {
if (!ComputerUtilCost.canPayCost(ability, attacker.getController(), false)) {
continue;
}
@@ -2312,7 +2312,7 @@ public class ComputerUtilCombat {
continue;
}
if (!ability.hasParam("KW") || !ComputerUtilCost.canPayCost(ability, controller)) {
if (!ability.hasParam("KW") || !ComputerUtilCost.canPayCost(ability, controller, false)) {
continue;
}
if (c != combatant) {
@@ -2346,7 +2346,7 @@ public class ComputerUtilCombat {
private final static Card canTransform(Card original) {
if (original.isDoubleFaced() && !original.isInAlternateState()) {
for (SpellAbility sa : original.getSpellAbilities()) {
if (sa.getApi() == ApiType.SetState && ComputerUtilCost.canPayCost(sa, original.getController())) {
if (sa.getApi() == ApiType.SetState && ComputerUtilCost.canPayCost(sa, original.getController(), false)) {
Card transformed = CardUtil.getLKICopy(original);
transformed.getCurrentState().copyFrom(original.getAlternateState(), true);
transformed.updateStateForView();

View File

@@ -73,7 +73,7 @@ public class ComputerUtilCost {
if (cost == null) {
return true;
}
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa);
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa, false);
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostRemoveCounter) {
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
@@ -233,7 +233,7 @@ public class ComputerUtilCost {
return true;
}
public static boolean checkForManaSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility) {
public static boolean checkForManaSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility, final boolean effect) {
// TODO cheating via autopay can still happen, need to get the real ai player from controlledBy
if (cost == null || !ai.isAI()) {
return true;
@@ -260,7 +260,7 @@ public class ComputerUtilCost {
c = AbilityUtils.calculateAmount(source, amount, sourceAbility);
}
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
CardCollectionView choices = aic.chooseSacrificeType(part.getType(), sourceAbility, c, exclude);
CardCollectionView choices = aic.chooseSacrificeType(part.getType(), sourceAbility, effect, c, exclude);
if (choices != null) {
list.addAll(choices);
}
@@ -523,7 +523,7 @@ public class ComputerUtilCost {
* a {@link forge.game.player.Player} object.
* @return a boolean.
*/
public static boolean canPayCost(final SpellAbility sa, final Player player) {
public static boolean canPayCost(final SpellAbility sa, final Player player, final boolean effect) {
if (sa.getActivatingPlayer() == null) {
sa.setActivatingPlayer(player); // complaints on NPE had came before this line was added.
}
@@ -585,7 +585,7 @@ public class ComputerUtilCost {
if (sa.hasParam("Crew")) { // put under checkTapTypeCost?
for (final CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostTapType && part.getType().contains("+withTotalPowerGE")) {
return new AiCostDecision(player, sa).visit((CostTapType)part) != null;
return new AiCostDecision(player, sa, false).visit((CostTapType)part) != null;
}
}
}
@@ -632,7 +632,7 @@ public class ComputerUtilCost {
}
}
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded)
return ComputerUtilMana.canPayManaCost(sa, player, extraManaNeeded, effect)
&& CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa);
} // canPayCost()
@@ -699,7 +699,7 @@ public class ComputerUtilCost {
if (part instanceof CostPayLife) {
final CostPayLife lifeCost = (CostPayLife) part;
Integer amount = lifeCost.convertAmount();
if (payer.getLife() > (amount + 1) && payer.canPayLife(amount)) {
if (payer.getLife() > (amount + 1) && payer.canPayLife(amount, true)) {
final int landsize = payer.getLandsInPlay().size() + 1;
for (Card c : payer.getCardsIn(ZoneType.Hand)) {
// Check if the AI has enough lands to play the card
@@ -767,7 +767,7 @@ public class ComputerUtilCost {
return false;
}
public static int getMaxXValue(SpellAbility sa, Player ai) {
public static int getMaxXValue(SpellAbility sa, Player ai, final boolean effect) {
final Card source = sa.getHostCard();
SpellAbility root = sa.getRootAbility();
final Cost abCost = root.getPayCosts();
@@ -779,7 +779,7 @@ public class ComputerUtilCost {
Integer val = null;
if (root.costHasManaX()) {
val = ComputerUtilMana.determineLeftoverMana(root, ai);
val = ComputerUtilMana.determineLeftoverMana(root, ai, effect);
}
if (sa.usesTargeting()) {
@@ -795,7 +795,7 @@ public class ComputerUtilCost {
}
}
val = ObjectUtils.min(val, abCost.getMaxForNonManaX(root, ai));
val = ObjectUtils.min(val, abCost.getMaxForNonManaX(root, ai, false));
if (val != null && val > 0) {
// filter cost parts for preferences, don't choose X > than possible preferences

View File

@@ -48,23 +48,23 @@ import java.util.*;
public class ComputerUtilMana {
private final static boolean DEBUG_MANA_PAYMENT = false;
public static boolean canPayManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
public static boolean canPayManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean effect) {
cost = new ManaCostBeingPaid(cost); //check copy of cost so it doesn't modify the exist cost being paid
return payManaCost(cost, sa, ai, true, true);
return payManaCost(cost, sa, ai, true, true, effect);
}
public static boolean canPayManaCost(final SpellAbility sa, final Player ai, final int extraMana) {
return payManaCost(sa, ai, true, extraMana, true);
public static boolean canPayManaCost(final SpellAbility sa, final Player ai, final int extraMana, final boolean effect) {
return payManaCost(sa, ai, true, extraMana, true, effect);
}
public static boolean payManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
return payManaCost(cost, sa, ai, false, true);
public static boolean payManaCost(ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean effect) {
return payManaCost(cost, sa, ai, false, true, effect);
}
public static boolean payManaCost(final Player ai, final SpellAbility sa) {
return payManaCost(sa, ai, false, 0, true);
public static boolean payManaCost(final Player ai, final SpellAbility sa, final boolean effect) {
return payManaCost(sa, ai, false, 0, true, effect);
}
private static boolean payManaCost(final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) {
private static boolean payManaCost(final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable, final boolean effect) {
ManaCostBeingPaid cost = calculateManaCost(sa, test, extraMana);
return payManaCost(cost, sa, ai, test, checkPlayable);
return payManaCost(cost, sa, ai, test, checkPlayable, effect);
}
private static void refundMana(List<Mana> manaSpent, Player ai, SpellAbility sa) {
@@ -82,7 +82,7 @@ public class ComputerUtilMana {
*/
public static int getConvergeCount(final SpellAbility sa, final Player ai) {
ManaCostBeingPaid cost = calculateManaCost(sa, true, 0);
if (payManaCost(cost, sa, ai, true, true)) {
if (payManaCost(cost, sa, ai, true, true, false)) {
return cost.getSunburst();
}
return 0;
@@ -93,7 +93,7 @@ public class ComputerUtilMana {
if (ai == null || sa == null)
return false;
sa.setActivatingPlayer(ai);
return payManaCost(sa, ai, true, 0, false);
return payManaCost(sa, ai, true, 0, false, false);
}
private static Integer scoreManaProducingCard(final Card card) {
@@ -326,7 +326,7 @@ public class ComputerUtilMana {
continue;
}
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma.getHostCard(), ma)) {
if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma.getHostCard(), ma, ma.isTrigger())) {
continue;
}
@@ -642,7 +642,7 @@ public class ComputerUtilMana {
SpellAbility saPayment = chooseManaAbility(cost, sa, ai, toPay, saList, true);
if (saPayment == null) {
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2)) {
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2, false)) {
break; // cannot pay
}
@@ -673,7 +673,7 @@ public class ComputerUtilMana {
return manaSources;
} // getManaSourcesToPayCost()
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable) {
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable, boolean effect) {
AiCardMemory.clearMemorySet(ai, MemorySet.PAYS_TAP_COST);
AiCardMemory.clearMemorySet(ai, MemorySet.PAYS_SAC_COST);
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
@@ -793,7 +793,7 @@ public class ComputerUtilMana {
}
if (saPayment == null) {
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2)
if ((!toPay.isPhyrexian() && !lifeInsteadOfBlack) || !ai.canPayLife(2, false)
|| (ai.getLife() <= 2 && !ai.cantLoseForZeroOrLessLife())) {
break; // cannot pay
}
@@ -816,7 +816,7 @@ public class ComputerUtilMana {
}
if (!test) {
ai.payLife(2, sa.getHostCard());
ai.payLife(2, sa.getHostCard(), false);
}
continue;
}
@@ -852,7 +852,7 @@ public class ComputerUtilMana {
Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
} else {
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment, effect))) {
saList.remove(saPayment);
continue;
}
@@ -1547,7 +1547,7 @@ public class ComputerUtilMana {
for (int i = 0; i < 10; i++) {
mCost = ManaCost.combine(mCost, mkCost);
ManaCostBeingPaid mcbp = new ManaCostBeingPaid(mCost);
if (!canPayManaCost(mcbp, sa, sa.getActivatingPlayer())) {
if (!canPayManaCost(mcbp, sa, sa.getActivatingPlayer(), true)) {
sa.getHostCard().setSVar("NumTimes", "Number$" + i);
break;
}
@@ -1866,9 +1866,9 @@ public class ComputerUtilMana {
* @return a int.
* @since 1.0.15
*/
public static int determineLeftoverMana(final SpellAbility sa, final Player player) {
public static int determineLeftoverMana(final SpellAbility sa, final Player player, final boolean effect) {
for (int i = 1; i < 100; i++) {
if (!canPayManaCost(sa.getRootAbility(), player, i)) {
if (!canPayManaCost(sa.getRootAbility(), player, i, effect)) {
return i - 1;
}
}
@@ -1889,13 +1889,13 @@ public class ComputerUtilMana {
* @return a int.
* @since 1.5.59
*/
public static int determineLeftoverMana(final SpellAbility sa, final Player player, final String shardColor) {
public static int determineLeftoverMana(final SpellAbility sa, final Player player, final String shardColor, final boolean effect) {
ManaCost origCost = sa.getRootAbility().getPayCosts().getTotalMana();
String shardSurplus = shardColor;
for (int i = 1; i < 100; i++) {
ManaCost extra = new ManaCost(new ManaCostParser(shardSurplus));
if (!canPayManaCost(new ManaCostBeingPaid(ManaCost.combine(origCost, extra)), sa, player)) {
if (!canPayManaCost(new ManaCostBeingPaid(ManaCost.combine(origCost, extra)), sa, player, effect)) {
return i - 1;
}
shardSurplus += " " + shardColor;
@@ -1941,7 +1941,7 @@ public class ComputerUtilMana {
final Card offering = sa.getSacrificedAsOffering();
offering.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, null, null);
sa.getHostCard().getGame().getAction().sacrifice(offering, sa, false, null, null);
}
sa.resetSacrificedAsOffering();
}
@@ -1949,7 +1949,7 @@ public class ComputerUtilMana {
final Card emerge = sa.getSacrificedAsEmerge();
emerge.setUsedToPay(false);
if (costIsPaid && !test) {
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, null, null);
sa.getHostCard().getGame().getAction().sacrifice(emerge, sa, false, null, null);
}
sa.resetSacrificedAsEmerge();
}

View File

@@ -170,7 +170,7 @@ public class PlayerControllerAi extends PlayerController {
payingPlayer = ability.getHostCard().getEnchantingCard().getController();
}
int number = ComputerUtilMana.determineLeftoverMana(ability, player);
int number = ComputerUtilMana.determineLeftoverMana(ability, player, false);
if (logic.startsWith("MaxMana.") || logic.startsWith("PowerLeakMaxMana.")) {
number = Math.min(number, Integer.parseInt(logic.substring(logic.indexOf(".") + 1)));
@@ -535,7 +535,7 @@ public class PlayerControllerAi extends PlayerController {
public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) {
if (canSetupTargets)
brains.doTrigger(effectSA, true); // first parameter does not matter, since return value won't be used
ComputerUtil.playNoStack(player, effectSA, getGame());
ComputerUtil.playNoStack(player, effectSA, getGame(), true);
}
@Override
@@ -692,6 +692,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public boolean payManaOptional(Card c, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose) {
// TODO replace with EmptySa
final Ability ability = new AbilityStatic(c, cost, null) { @Override public void resolve() {} };
ability.setActivatingPlayer(c.getController());
@@ -713,8 +714,8 @@ public class PlayerControllerAi extends PlayerController {
}
// - End of hack for Exile a card from library Cumulative Upkeep -
if (ComputerUtilCost.canPayCost(ability, c.getController())) {
ComputerUtil.playNoStack(c.getController(), ability, getGame());
if (ComputerUtilCost.canPayCost(ability, c.getController(), true)) {
ComputerUtil.playNoStack(c.getController(), ability, getGame(), true);
return true;
}
return false;
@@ -1009,13 +1010,14 @@ public class PlayerControllerAi extends PlayerController {
@Override
public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, FCollectionView<Player> allPayers) {
final Card source = sa.getHostCard();
// TODO replace with EmptySa
final Ability emptyAbility = new AbilityStatic(source, cost, sa.getTargetRestrictions()) { @Override public void resolve() { } };
emptyAbility.setActivatingPlayer(player);
emptyAbility.setTriggeringObjects(sa.getTriggeringObjects());
emptyAbility.setSVars(sa.getSVars());
emptyAbility.setXManaCostPaid(sa.getRootAbility().getXManaCostPaid());
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player)) {
ComputerUtil.playNoStack(player, emptyAbility, getGame()); // AI needs something to resolve to pay that cost
if (ComputerUtilCost.willPayUnlessCost(sa, player, cost, alreadyPaid, allPayers) && ComputerUtilCost.canPayCost(emptyAbility, player, true)) {
ComputerUtil.playNoStack(player, emptyAbility, getGame(), true); // AI needs something to resolve to pay that cost
return true;
}
return false;
@@ -1069,7 +1071,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public void playTrigger(Card host, WrappedAbility wrapperAbility, boolean isMandatory) {
if (prepareSingleSa(host, wrapperAbility, isMandatory)) {
ComputerUtil.playNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, getGame());
ComputerUtil.playNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, getGame(), true);
}
}
@@ -1138,10 +1140,10 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */, ManaConversionMatrix matrix, boolean isActivatedSa) {
public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt /* ai needs hints as well */, ManaConversionMatrix matrix, boolean effect) {
// TODO Auto-generated method stub
ManaCostBeingPaid cost = isActivatedSa ? ComputerUtilMana.calculateManaCost(sa, false, 0) : new ManaCostBeingPaid(toPay);
return ComputerUtilMana.payManaCost(cost, sa, player);
ManaCostBeingPaid cost = !effect ? ComputerUtilMana.calculateManaCost(sa, false, 0) : new ManaCostBeingPaid(toPay);
return ComputerUtilMana.payManaCost(cost, sa, player, effect);
}
@Override
@@ -1347,7 +1349,7 @@ public class PlayerControllerAi extends PlayerController {
}
}
if (ComputerUtilCost.canPayCost(fullCostSa, player)) {
if (ComputerUtilCost.canPayCost(fullCostSa, player, false)) {
chosenOptCosts.add(opt);
costSoFar.add(opt.getCost());
}
@@ -1372,7 +1374,7 @@ public class PlayerControllerAi extends PlayerController {
for (int i = 0; i < max; i++) {
costSoFar.add(cost);
SpellAbility fullCostSa = sa.copyWithDefinedCost(costSoFar);
if (ComputerUtilCost.canPayCost(fullCostSa, player)) {
if (ComputerUtilCost.canPayCost(fullCostSa, player, sa.isTrigger())) {
chosenAmount++;
} else {
break;

View File

@@ -647,7 +647,7 @@ public class SpecialCardAi {
public static class GoblinPolkaBand {
public static boolean consider(final Player ai, final SpellAbility sa) {
int maxPotentialTgts = Lists.newArrayList(Iterables.filter(ai.getOpponents().getCreaturesInPlay(), CardPredicates.Presets.UNTAPPED)).size();
int maxPotentialPayment = ComputerUtilMana.determineLeftoverMana(sa, ai, "R");
int maxPotentialPayment = ComputerUtilMana.determineLeftoverMana(sa, ai, "R", false);
int numTgts = Math.min(maxPotentialPayment, maxPotentialTgts);
if (numTgts == 0) {
@@ -924,7 +924,7 @@ public class SpecialCardAi {
public static Card considerCardFromList(final CardCollection fetchList) {
for (Card c : CardLists.filter(fetchList, Predicates.or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.CREATURES))) {
for (SpellAbility ab : c.getSpellAbilities()) {
if (ab.isAbility() && !ab.isTrigger()) {
if (ab.isActivatedAbility()) {
Player controller = c.getController();
boolean wasCaged = false;
for (Card caged : CardLists.filter(controller.getCardsIn(ZoneType.Exile),
@@ -994,7 +994,7 @@ public class SpecialCardAi {
}
// Set PayX here to maximum value.
int tokenSize = ComputerUtilCost.getMaxXValue(sa, ai);
int tokenSize = ComputerUtilCost.getMaxXValue(sa, ai, false);
// Some basic strategy for Momir
if (tokenSize < 2) {
@@ -1014,7 +1014,7 @@ public class SpecialCardAi {
// Multiple Choice
public static class MultipleChoice {
public static boolean consider(final Player ai, final SpellAbility sa) {
int maxX = ComputerUtilCost.getMaxXValue(sa, ai);
int maxX = ComputerUtilCost.getMaxXValue(sa, ai, false);
if (maxX == 0) {
return false;
@@ -1604,7 +1604,7 @@ public class SpecialCardAi {
if (topGY == null
|| !topGY.isCreature()
|| ComputerUtilCard.evaluateCreature(creatHand) > ComputerUtilCard.evaluateCreature(topGY) + 80) {
return numCreatsInHand > 1 || !ComputerUtilMana.canPayManaCost(creatHand.getSpellPermanent(), ai, 0);
return numCreatsInHand > 1 || !ComputerUtilMana.canPayManaCost(creatHand.getSpellPermanent(), ai, 0, false);
}
}
@@ -1769,7 +1769,7 @@ public class SpecialCardAi {
int CMC = ab.getPayCosts().getTotalMana() != null ? ab.getPayCosts().getTotalMana().getCMC() : 0;
int Xcount = ab.getPayCosts().getTotalMana() != null ? ab.getPayCosts().getTotalMana().countX() : 0;
if ((Xcount == 0 && CMC == 0) || ComputerUtilMana.canPayManaCost(ab, ai, selfCMC + minManaAdj)) {
if ((Xcount == 0 && CMC == 0) || ComputerUtilMana.canPayManaCost(ab, ai, selfCMC + minManaAdj, false)) {
if (src.isInstant() || src.isSorcery()) {
// instants and sorceries are one-shot, so only treat them as 1/2 value for the purpose of meeting minimum
// castable cards in graveyard requirements

View File

@@ -60,7 +60,7 @@ public abstract class SpellAbilityAi {
if (sa.hasParam("AICheckCanPlayWithDefinedX")) {
// FIXME: can this somehow be simplified without the need for an extra AI hint?
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai, false));
}
if (!checkConditions(ai, sa, sa.getConditions())) {
@@ -104,7 +104,7 @@ public abstract class SpellAbilityAi {
if (!con.getManaSpent().isEmpty()) {
// need to use ManaCostBeingPaid check, can't use Cost#canPay
ManaCostBeingPaid paid = new ManaCostBeingPaid(new ManaCost(new ManaCostParser(con.getManaSpent())));
if (ComputerUtilMana.canPayManaCost(paid, sa, ai)) {
if (ComputerUtilMana.canPayManaCost(paid, sa, ai, sa.isTrigger())) {
con.setManaSpent("");
}
}
@@ -169,7 +169,7 @@ public abstract class SpellAbilityAi {
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
// this evaluation order is currently intentional as it does more stuff that helps avoiding some crashes
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
if (!ComputerUtilCost.canPayCost(sa, aiPlayer, true) && !mandatory) {
return false;
}
@@ -253,7 +253,7 @@ public abstract class SpellAbilityAi {
*/
protected static boolean isSorcerySpeed(final SpellAbility sa) {
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|| (sa.getRootAbility().isAbility() && sa.getRestrictions().isSorcerySpeed())
|| (sa.getRootAbility().isActivatedAbility() && sa.getRestrictions().isSorcerySpeed())
|| (sa.getRootAbility().isAdventure() && sa.getHostCard().getState(CardStateName.Adventure).getType().isSorcery())
|| (sa.isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
}

View File

@@ -71,14 +71,14 @@ public class AnimateAi extends SpellAbilityAi {
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
ai.getWeakestOpponent(), topStack.getHostCard(), topStack);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true));
ComputerUtilCard.sortByEvaluateCreature(list);
if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai)) {
if (!list.isEmpty() && list.size() == nToSac && ComputerUtilCost.canPayCost(sa, ai, sa.isTrigger())) {
Card animatedCopy = becomeAnimated(source, sa);
list.add(animatedCopy);
list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(),
topStack);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true));
if (ComputerUtilCard.evaluateCreature(animatedCopy) < ComputerUtilCard.evaluateCreature(list.get(0))
&& list.contains(animatedCopy)) {
return true;
@@ -137,7 +137,7 @@ public class AnimateAi extends SpellAbilityAi {
if (sa.costHasManaX() && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, aiPlayer);
final int xPay = ComputerUtilCost.getMaxXValue(sa, aiPlayer, sa.isTrigger());
sa.setXManaCostPaid(xPay);
}
@@ -343,7 +343,7 @@ public class AnimateAi extends SpellAbilityAi {
if (worst.isLand()) {
// e.g. Clan Guildmage, make sure we're not using the same land we want to animate to activate the ability
holdAnimatedTillMain2(ai, worst);
if (!ComputerUtilMana.canPayManaCost(sa, ai, 0)) {
if (!ComputerUtilMana.canPayManaCost(sa, ai, 0, sa.isTrigger())) {
releaseHeldTillMain2(ai, worst);
return false;
}

View File

@@ -114,7 +114,7 @@ public class AttachAi extends SpellAbilityAi {
if (abCost.getTotalMana().countX() > 0 && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value. (Endless Scream and Venarian Gold)
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
if (xPay == 0) {
return false;
@@ -353,14 +353,14 @@ public class AttachAi extends SpellAbilityAi {
public boolean apply(final Card c) {
//Check for cards that can be sacrificed in response
for (final SpellAbility ability : c.getAllSpellAbilities()) {
if (ability.isAbility()) {
if (ability.isActivatedAbility()) {
final Cost cost = ability.getPayCosts();
for (final CostPart part : cost.getCostParts()) {
if (!(part instanceof CostSacrifice)) {
continue;
}
CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController(), false)) {
return false;
}
}

View File

@@ -81,7 +81,7 @@ public class ChangeTargetsAi extends SpellAbilityAi {
// e.g. Spellskite or a creature receiving its ability that requires Phyrexian mana P/U
int potentialDmg = ComputerUtil.predictDamageFromSpell(topSa, aiPlayer);
ManaCost normalizedMana = manaCost.getNormalizedMana();
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer);
boolean canPay = ComputerUtilMana.canPayManaCost(new ManaCostBeingPaid(normalizedMana), sa, aiPlayer, false);
if (potentialDmg != -1 && potentialDmg <= payDamage && !canPay
&& topTargets.contains(aiPlayer)) {
// do not pay Phyrexian mana if the spell is a damaging one but it deals less damage or the same damage as we'll pay life

View File

@@ -347,7 +347,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (type != null) {
if (type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(xPay);
type = type.replace("X", Integer.toString(xPay));
}
@@ -392,7 +392,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (num != null) {
if (num.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
if (xPay == 0) return false;
xPay = Math.min(xPay, list.size());
sa.setXManaCostPaid(xPay);
@@ -502,7 +502,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
final String type = sa.getParam("ChangeType");
if (type != null && type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(xPay);
}
@@ -870,7 +870,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// X controls the minimum targets
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
sa.setXManaCostPaid(xPay);
@@ -2171,7 +2171,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
boolean setPayX = false;
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
toPay = ComputerUtilCost.getMaxXValue(sa, ai);
toPay = ComputerUtilCost.getMaxXValue(sa, ai, true);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}

View File

@@ -44,7 +44,7 @@ public class ChooseColorAi extends SpellAbilityAi {
return false;
}
// Set PayX here to maximum value.
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai, false));
return true;
}

View File

@@ -121,7 +121,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
SpellAbility paycost = new SpellAbility.EmptySa(sa.getHostCard(), player);
paycost.setPayCosts(unless);
if (ComputerUtilCost.willPayUnlessCost(sp, player, unless, false, new FCollection<>(player))
&& ComputerUtilCost.canPayCost(paycost, player)) {
&& ComputerUtilCost.canPayCost(paycost, player, true)) {
return sp;
}
}

View File

@@ -59,7 +59,7 @@ public class ChooseTypeAi extends SpellAbilityAi {
return false;
}
int maxX = ComputerUtilMana.determineLeftoverMana(sa, aiPlayer);
int maxX = ComputerUtilMana.determineLeftoverMana(sa, aiPlayer, false);
int avgPower = 0;
// predict the opposition

View File

@@ -82,7 +82,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
if (sa.costHasManaX() && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value. (Osgir)
final int xPay = ComputerUtilCost.getMaxXValue(sa, aiPlayer);
final int xPay = ComputerUtilCost.getMaxXValue(sa, aiPlayer, sa.isTrigger());
sa.setXManaCostPaid(xPay);
}

View File

@@ -113,7 +113,7 @@ public class CounterAi extends SpellAbilityAi {
boolean setPayX = false;
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
toPay = Math.min(ComputerUtilCost.getMaxXValue(sa, ai), usableManaSources + 1);
toPay = Math.min(ComputerUtilCost.getMaxXValue(sa, ai, true), usableManaSources + 1);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}
@@ -275,7 +275,7 @@ public class CounterAi extends SpellAbilityAi {
boolean setPayX = false;
if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) {
setPayX = true;
toPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
toPay = ComputerUtilCost.getMaxXValue(sa, ai, true);
} else {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}

View File

@@ -198,7 +198,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
// check if Spell with Strive is still playable
if (sa.isSpell() && sa.getHostCard().hasStartOfKeyword("Strive")) {
// if not remove target again and break list
if (!ComputerUtilCost.canPayCost(sa, ai)) {
if (!ComputerUtilCost.canPayCost(sa, ai, false)) {
sa.getTargets().remove(c);
break;
}

View File

@@ -373,7 +373,7 @@ public class CountersPutAi extends CountersAi {
if (amountStr.equals("X")) {
if (sa.getSVar(amountStr).equals("Count$xPaid")) {
// By default, set PayX here to maximum value (used for most SAs of this type).
amount = ComputerUtilCost.getMaxXValue(sa, ai);
amount = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
if (isClockwork) {
// Clockwork Avian and other similar cards: do not tap all mana for X,
@@ -769,7 +769,7 @@ public class CountersPutAi extends CountersAi {
}
// Spend all remaining mana to add X counters (eg. Hero of Leina Tower)
int payX = ComputerUtilCost.getMaxXValue(sa, ai);
int payX = ComputerUtilCost.getMaxXValue(sa, ai, true);
// Account for the possible presence of additional glyphs in cost (e.g. Mikaeus, the Lunarch; Primordial Hydra)
payX -= nonXGlyphs;

View File

@@ -83,7 +83,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
final int amount;
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilCost.getMaxXValue(sa, ai);
amount = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(amount);
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);

View File

@@ -149,7 +149,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
int amount;
boolean xPay = false;
if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai);
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
if (manaLeft == 0) {
return false;
@@ -301,7 +301,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
boolean xPay = false;
// Timecrafting has X R
if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai);
final int manaLeft = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
if (manaLeft == 0) {
return false;

View File

@@ -50,7 +50,7 @@ public class DamageAllAi extends SpellAbilityAi {
dmg = ComputerUtilMana.getConvergeCount(sa, ai);
}
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
x = ComputerUtilCost.getMaxXValue(sa, ai);
x = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
}
if (x == -1) {
if (determineOppToKill(ai, sa, source, dmg) != null) {
@@ -197,7 +197,7 @@ public class DamageAllAi extends SpellAbilityAi {
int dmg;
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(dmg);
} else {
dmg = AbilityUtils.calculateAmount(source, damage, sa);
@@ -276,7 +276,7 @@ public class DamageAllAi extends SpellAbilityAi {
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
dmg = ComputerUtilCost.getMaxXValue(sa, ai, true);
sa.setXManaCostPaid(dmg);
} else {
dmg = AbilityUtils.calculateAmount(source, damage, sa);

View File

@@ -91,7 +91,7 @@ public class DamageDealAi extends DamageAiBase {
}
// Set PayX here to maximum value.
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(dmg);
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.isInZone(ZoneType.Hand)) {
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
@@ -111,7 +111,7 @@ public class DamageDealAi extends DamageAiBase {
if (damage.equals("X")) {
if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) {
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
// Try not to waste spells like Blaze or Fireball on early targets, try to do more damage with them if possible
if (ai.getController().isAI()) {
@@ -959,7 +959,7 @@ public class DamageDealAi extends DamageAiBase {
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilCost.getMaxXValue(sa, ai);
dmg = ComputerUtilCost.getMaxXValue(sa, ai, true);
sa.setXManaCostPaid(dmg);
}
@@ -1007,9 +1007,9 @@ public class DamageDealAi extends DamageAiBase {
Player opponent = ai.getWeakestOpponent();
// TODO: somehow account for the possible cost reduction?
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"));
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"), false);
while (!ComputerUtilMana.canPayManaCost(sa, ai, dmg) && dmg > 0) {
while (!ComputerUtilMana.canPayManaCost(sa, ai, dmg, false) && dmg > 0) {
// TODO: ideally should never get here, currently put here as a precaution for complex mana base cases where the miscalculation might occur. Will remove later if it proves to never trigger.
dmg--;
System.out.println("Warning: AI could not pay mana cost for a XLifeDrain logic spell. Reducing X value to "+dmg);
@@ -1019,7 +1019,7 @@ public class DamageDealAi extends DamageAiBase {
// TODO: somehow generalize this calculation to allow other potential similar cards to function in the future
if ("Soul Burn".equals(sourceName)) {
Map<String, Integer> xByColor = Maps.newHashMap();
xByColor.put("B", dmg - ComputerUtilMana.determineLeftoverMana(sa, ai, "R"));
xByColor.put("B", dmg - ComputerUtilMana.determineLeftoverMana(sa, ai, "R", false));
source.setXManaCostPaidByColor(xByColor);
}
@@ -1123,7 +1123,7 @@ public class DamageDealAi extends DamageAiBase {
ManaCost total = ManaCost.combine(costSa, costAb);
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
// can we pay both costs?
if (ComputerUtilMana.canPayManaCost(combinedAb, ai, 0)) {
if (ComputerUtilMana.canPayManaCost(combinedAb, ai, 0, false)) {
return Pair.of(ab, Integer.parseInt(dmgDef));
}
}

View File

@@ -85,7 +85,7 @@ public class DelayedTriggerAi extends SpellAbilityAi {
ManaCost total = ManaCost.combine(costSa, costAb);
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
// can we pay both costs?
if (ComputerUtilMana.canPayManaCost(combinedAb, ai, 0)) {
if (ComputerUtilMana.canPayManaCost(combinedAb, ai, 0, true)) {
return true;
}
}
@@ -123,7 +123,7 @@ public class DelayedTriggerAi extends SpellAbilityAi {
}
AiPlayDecision decision = ((PlayerControllerAi) ai.getController()).getAi().canPlaySa(ab);
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
if (ComputerUtilMana.canPayManaCost(ab, ai, 0)) {
if (ComputerUtilMana.canPayManaCost(ab, ai, 0, true)) {
return true;
}
}

View File

@@ -124,7 +124,7 @@ public class DestroyAi extends SpellAbilityAi {
// If there's X in payment costs and it's tied to targeting, make sure we set the XManaCostPaid first
// (e.g. Heliod's Intervention)
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.getRootAbility().setXManaCostPaid(xPay);
}
@@ -139,7 +139,7 @@ public class DestroyAi extends SpellAbilityAi {
if (sa.getRootAbility().costHasManaX()) {
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
// need to set XPaid to get the right number for
sa.getRootAbility().setXManaCostPaid(maxTargets);
// need to check for maxTargets
@@ -179,14 +179,14 @@ public class DestroyAi extends SpellAbilityAi {
public boolean apply(final Card c) {
//Check for cards that can be sacrificed in response
for (final SpellAbility ability : c.getAllSpellAbilities()) {
if (ability.isAbility()) {
if (ability.isActivatedAbility()) {
final Cost cost = ability.getPayCosts();
for (final CostPart part : cost.getCostParts()) {
if (!(part instanceof CostSacrifice)) {
continue;
}
CostSacrifice sacCost = (CostSacrifice) part;
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController())) {
if (sacCost.payCostFromSource() && ComputerUtilCost.canPayCost(ability, c.getController(), false)) {
return false;
}
}

View File

@@ -85,7 +85,7 @@ public class DestroyAllAi extends SpellAbilityAi {
if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(xPay);
valid = valid.replace("X", Integer.toString(xPay));
}

View File

@@ -94,7 +94,7 @@ public class DigAi extends SpellAbilityAi {
manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
}
int numCards = ComputerUtilCost.getMaxXValue(sa, ai) - manaToSave;
int numCards = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()) - manaToSave;
if (numCards <= 0) {
return false;
}
@@ -144,7 +144,7 @@ public class DigAi extends SpellAbilityAi {
// Triggers that ask to pay {X} (e.g. Depala, Pilot Exemplar).
if (sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayXButSaveMana")) {
int manaToSave = Integer.parseInt(TextUtil.split(sa.getParam("AILogic"), '.')[1]);
int numCards = ComputerUtilCost.getMaxXValue(sa, ai) - manaToSave;
int numCards = ComputerUtilCost.getMaxXValue(sa, ai, true) - manaToSave;
if (numCards <= 0) {
return mandatory;
}

View File

@@ -79,7 +79,7 @@ public class DigUntilAi extends SpellAbilityAi {
// Set PayX here to maximum value.
SpellAbility root = sa.getRootAbility();
if (root.getXManaCostPaid() == null) {
int numCards = ComputerUtilCost.getMaxXValue(sa, ai);
int numCards = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
if (numCards <= 0) {
return false;
}

View File

@@ -76,7 +76,7 @@ public class DiscardAi extends SpellAbilityAi {
if (sa.hasParam("NumCards")) {
if (sa.getParam("NumCards").equals("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilCost.getMaxXValue(sa, ai), ai.getWeakestOpponent()
final int cardsToDiscard = Math.min(ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()), ai.getWeakestOpponent()
.getCardsIn(ZoneType.Hand).size());
if (cardsToDiscard < 1) {
return false;
@@ -151,7 +151,7 @@ public class DiscardAi extends SpellAbilityAi {
for (Player opp : opps) {
if (opp.getCardsIn(ZoneType.Hand).isEmpty() && !ComputerUtil.activateForCost(sa, ai)) {
continue;
} else if (!opp.canDiscardBy(sa)) { // e.g. Tamiyo, Collector of Tales
} else if (!opp.canDiscardBy(sa, true)) { // e.g. Tamiyo, Collector of Tales
continue;
}
if (sa.usesTargeting()) {
@@ -190,7 +190,7 @@ public class DiscardAi extends SpellAbilityAi {
}
if ("X".equals(sa.getParam("RevealNumber")) && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int cardsToDiscard = Math.min(ComputerUtilCost.getMaxXValue(sa, ai), ai.getWeakestOpponent()
final int cardsToDiscard = Math.min(ComputerUtilCost.getMaxXValue(sa, ai, true), ai.getWeakestOpponent()
.getCardsIn(ZoneType.Hand).size());
sa.setXManaCostPaid(cardsToDiscard);
}

View File

@@ -103,7 +103,7 @@ public class DrawAi extends SpellAbilityAi {
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source,sa)) {
AiCostDecision aiDecisions = new AiCostDecision(ai, sa);
AiCostDecision aiDecisions = new AiCostDecision(ai, sa, false);
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostDiscard) {
PaymentDecision decision = part.accept(aiDecisions);
@@ -255,7 +255,7 @@ public class DrawAi extends SpellAbilityAi {
if (drawback && root.getXManaCostPaid() != null) {
numCards = root.getXManaCostPaid();
} else {
numCards = ComputerUtilCost.getMaxXValue(sa, ai);
numCards = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
// try not to overdraw
int safeDraw = Math.abs(Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3));
if (source.isInstant() || source.isSorcery()) { safeDraw++; } // card will be spent

View File

@@ -123,7 +123,7 @@ public class EffectAi extends SpellAbilityAi {
} else if (logic.equals("WillCastCreature") && ai.isAI()) {
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
SpellAbility saCreature = aic.predictSpellToCastInMain2(ApiType.PermanentCreature);
randomReturn = saCreature != null && ComputerUtilMana.canPayManaCost(saCreature, ai, 0);
randomReturn = saCreature != null && ComputerUtilMana.canPayManaCost(saCreature, ai, 0, false);
} else if (logic.equals("Always")) {
randomReturn = true;
} else if (logic.equals("Main1")) {

View File

@@ -39,7 +39,7 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
}
if (logic.equals("MaxX")) {
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai, true));
}
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();

View File

@@ -131,7 +131,7 @@ public class LifeGainAi extends SpellAbilityAi {
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(xPay);
lifeAmount = xPay;
} else {
@@ -218,7 +218,7 @@ public class LifeGainAi extends SpellAbilityAi {
final String amountStr = sa.getParam("LifeAmount");
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, true);
sa.setXManaCostPaid(xPay);
}

View File

@@ -6,7 +6,6 @@ import com.google.common.base.Predicates;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -40,7 +39,7 @@ public class LifeLoseAi extends SpellAbilityAi {
amount = root.getXManaCostPaid();
} else if (root.getPayCosts() != null && root.getPayCosts().hasXInAnyCostPart()) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
root.setXManaCostPaid(xPay);
amount = xPay;
}
@@ -73,7 +72,7 @@ public class LifeLoseAi extends SpellAbilityAi {
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilMana.determineLeftoverMana(sa, ai);
amount = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
@@ -107,7 +106,7 @@ public class LifeLoseAi extends SpellAbilityAi {
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = ComputerUtilCost.getMaxXValue(sa, ai);
amount = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(amount);
} else {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
@@ -173,7 +172,7 @@ public class LifeLoseAi extends SpellAbilityAi {
int amount = 0;
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, true);
sa.setXManaCostPaid(xPay);
amount = xPay;
} else {

View File

@@ -44,7 +44,7 @@ public class LifeSetAi extends SpellAbilityAi {
// we shouldn't have to worry too much about PayX for SetLife
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(xPay);
amount = xPay;
} else {
@@ -114,7 +114,7 @@ public class LifeSetAi extends SpellAbilityAi {
int amount;
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, true);
sa.setXManaCostPaid(xPay);
amount = xPay;
} else {

View File

@@ -79,7 +79,7 @@ public class ManifestAi extends SpellAbilityAi {
if (sa.getSVar("X").equals("Count$xPaid")) {
// Handle either Manifest X cards, or Manifest 1 card and give it X P1P1s
// Set PayX here to maximum value.
int x = ComputerUtilCost.getMaxXValue(sa, ai);
int x = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(x);
if (x <= 0) {
return false;

View File

@@ -221,6 +221,6 @@ public class MillAi extends SpellAbilityAi {
cardsToDiscard = Math.min(ai.getCardsIn(ZoneType.Library).size() - 5, cardsToDiscard);
}
return Math.min(ComputerUtilCost.getMaxXValue(sa, ai), cardsToDiscard);
return Math.min(ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()), cardsToDiscard);
}
}

View File

@@ -104,7 +104,7 @@ public class PermanentAi extends SpellAbilityAi {
ManaCost mana = sa.getPayCosts().getTotalMana();
if (mana.countX() > 0) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, false);
final Card source = sa.getHostCard();
if (source.hasConverge()) {
int nColors = ComputerUtilMana.getConvergeCount(sa, ai);
@@ -141,7 +141,7 @@ public class PermanentAi extends SpellAbilityAi {
int generic = paidCost.getGenericManaAmount();
// Set PayX here to maximum value.
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, false);
// currently cards with SacToReduceCost reduce by 2 generic
xPay = Math.min(xPay, generic / 2);
sa.setXManaCostPaid(xPay);
@@ -155,7 +155,7 @@ public class PermanentAi extends SpellAbilityAi {
for (int i = 0; i < 10; i++) {
mCost = ManaCost.combine(mCost, mkCost);
ManaCostBeingPaid mcbp = new ManaCostBeingPaid(mCost);
if (!ComputerUtilMana.canPayManaCost(mcbp, sa, ai)) {
if (!ComputerUtilMana.canPayManaCost(mcbp, sa, ai, false)) {
card.setKickerMagnitude(i);
sa.setSVar("Multikicker", String.valueOf(i));
break;
@@ -181,7 +181,7 @@ public class PermanentAi extends SpellAbilityAi {
emptyAbility.setTargetRestrictions(sa.getTargetRestrictions());
emptyAbility.setActivatingPlayer(ai);
if (!ComputerUtilCost.canPayCost(emptyAbility, ai)) {
if (!ComputerUtilCost.canPayCost(emptyAbility, ai, true)) {
// AiPlayDecision.AnotherTime
return false;
}

View File

@@ -67,7 +67,7 @@ public class PermanentCreatureAi extends PermanentAi {
if (ph.isPlayerTurn(ai) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
if (game.getReplacementHandler().wouldPhaseBeSkipped(ai, "BeginCombat"))
return false;
if (ComputerUtilCost.canPayCost(sa.getHostCard().getSpellPermanent(), ai)) {
if (ComputerUtilCost.canPayCost(sa.getHostCard().getSpellPermanent(), ai, false)) {
//do not dash if creature can be played normally
return false;
}

View File

@@ -76,7 +76,7 @@ public class PlayAi extends SpellAbilityAi {
return false;
ManaCost mana = sa.getPayCosts().getTotalMana();
if (mana.countX() > 0) {
int amount = ComputerUtilCost.getMaxXValue(sa, ai);
int amount = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
if (amount < ComputerUtilCard.getBestAI(cards).getCMC())
return false;
int totalCMC = 0;

View File

@@ -63,7 +63,7 @@ public class PumpAi extends PumpAiBase {
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
} else if (aiLogic.equals("SwitchPT")) {
// Some more AI would be even better, but this is a good start to prevent spamming
if (sa.isAbility() && sa.getActivationsThisTurn() > 0 && !sa.usesTargeting()) {
if (sa.isActivatedAbility() && sa.getActivationsThisTurn() > 0 && !sa.usesTargeting()) {
// Will prevent flipping back and forth
return false;
}
@@ -253,7 +253,7 @@ public class PumpAi extends PumpAiBase {
// Donate step 1 - try to target an opponent, preferably one who does not have a donate target yet
return SpecialCardAi.Donate.considerTargetingOpponent(ai, sa);
} else if (aiLogic.equals("InfernoOfTheStarMounts")) {
int numRedMana = ComputerUtilMana.determineLeftoverMana(sa, ai, "R");
int numRedMana = ComputerUtilMana.determineLeftoverMana(sa, ai, "R", false);
int currentPower = source.getNetPower();
if (currentPower < 20 && currentPower + numRedMana >= 20) {
return true;
@@ -284,7 +284,7 @@ public class PumpAi extends PumpAiBase {
int defense;
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
if (sourceName.equals("Necropolis Fiend")) {
xPay = Math.min(xPay, sa.getActivatingPlayer().getCardsIn(ZoneType.Graveyard).size());
sa.setSVar("X", Integer.toString(xPay));
@@ -305,7 +305,7 @@ public class PumpAi extends PumpAiBase {
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
if (root.getXManaCostPaid() == null) {
final int xPay = ComputerUtilCost.getMaxXValue(root, ai);
final int xPay = ComputerUtilCost.getMaxXValue(root, ai, sa.isTrigger());
root.setXManaCostPaid(xPay);
attack = xPay;
} else {
@@ -531,7 +531,7 @@ public class PumpAi extends PumpAiBase {
@Override
public boolean apply(Card card) {
for (SpellAbility sa : card.getSpellAbilities()) {
if (sa.isAbility()) {
if (sa.isActivatedAbility()) {
return true;
}
}
@@ -681,7 +681,7 @@ public class PumpAi extends PumpAiBase {
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
if (root.getXManaCostPaid() == null) {
final int xPay = ComputerUtilCost.getMaxXValue(root, ai);
final int xPay = ComputerUtilCost.getMaxXValue(root, ai, true);
root.setXManaCostPaid(xPay);
defense = xPay;
} else {
@@ -695,7 +695,7 @@ public class PumpAi extends PumpAiBase {
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
if (root.getXManaCostPaid() == null) {
final int xPay = ComputerUtilCost.getMaxXValue(root, ai);
final int xPay = ComputerUtilCost.getMaxXValue(root, ai, true);
root.setXManaCostPaid(xPay);
attack = xPay;
} else {
@@ -750,7 +750,7 @@ public class PumpAi extends PumpAiBase {
if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
if (root.getXManaCostPaid() == null) {
// X is not set yet
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
root.setXManaCostPaid(xPay);
attack = xPay;
} else {
@@ -764,7 +764,7 @@ public class PumpAi extends PumpAiBase {
if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
if (root.getXManaCostPaid() == null) {
// X is not set yet
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
root.setXManaCostPaid(xPay);
defense = xPay;
} else {

View File

@@ -34,7 +34,7 @@ public class RepeatAi extends SpellAbilityAi {
return false;
}
// Set PayX here to maximum value.
final int max = ComputerUtilCost.getMaxXValue(sa, ai);
final int max = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.setXManaCostPaid(max);
return max > 0;
}

View File

@@ -4,7 +4,6 @@ import java.util.List;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
@@ -88,7 +87,7 @@ public class SacrificeAi extends SpellAbilityAi {
}
}
if (!destroy) {
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(sa));
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(sa, true));
} else {
if (!CardLists.getKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
// human can choose to destroy indestructibles
@@ -102,7 +101,7 @@ public class SacrificeAi extends SpellAbilityAi {
if (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
// Set PayX here to maximum value.
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()), amount));
}
final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
@@ -131,7 +130,7 @@ public class SacrificeAi extends SpellAbilityAi {
if (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) {
// Set PayX here to maximum value.
amount = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount);
amount = Math.min(ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()), amount);
}
List<Card> humanList = null;
@@ -183,7 +182,7 @@ public class SacrificeAi extends SpellAbilityAi {
if (!targetable.isEmpty()) {
CardCollection priorityTgts = new CardCollection();
if (p.isOpponentOf(ai)) {
priorityTgts.addAll(CardLists.filter(targetable, CardPredicates.canBeSacrificedBy(sa)));
priorityTgts.addAll(CardLists.filter(targetable, CardPredicates.canBeSacrificedBy(sa, true)));
if (!priorityTgts.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestAI(priorityTgts));
} else {
@@ -191,7 +190,7 @@ public class SacrificeAi extends SpellAbilityAi {
}
} else {
for (Card c : targetable) {
if (c.canBeSacrificedBy(sa) && (c.hasSVar("SacMe") || (c.isCreature() && ComputerUtilCard.evaluateCreature(c) <= 135)) && !c.equals(sa.getHostCard())) {
if (c.canBeSacrificedBy(sa, true) && (c.hasSVar("SacMe") || (c.isCreature() && ComputerUtilCard.evaluateCreature(c) <= 135)) && !c.equals(sa.getHostCard())) {
priorityTgts.add(c);
}
}

View File

@@ -44,7 +44,7 @@ public class SetStateAi extends SpellAbilityAi {
}
if (sa.getSVar("X").equals("Count$xPaid")) {
final int xPay = ComputerUtilCost.getMaxXValue(sa, aiPlayer);
final int xPay = ComputerUtilCost.getMaxXValue(sa, aiPlayer, sa.isTrigger());
sa.setXManaCostPaid(xPay);
}

View File

@@ -30,7 +30,7 @@ public class StoreSVarAi extends SpellAbilityAi {
if (sa.hasParam("AILogic")) {
if (sa.getPayCosts().getTotalMana().countX() > 0 && source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to half the remaining mana to allow for Main 2 and other combat shenanigans.
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai) / 2;
final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai, sa.isTrigger()) / 2;
if (xPay == 0) { return false; }
sa.setXManaCostPaid(xPay);
}

View File

@@ -63,7 +63,7 @@ public class TapAi extends TapAiBase {
if ("X".equals(sa.getTargetRestrictions().getMinTargets()) && sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()));
}
sa.resetTargets();

View File

@@ -98,7 +98,7 @@ public class TokenAi extends SpellAbilityAi {
}
if (sa.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
x = ComputerUtilCost.getMaxXValue(sa, ai);
x = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
sa.getRootAbility().setXManaCostPaid(x);
}
if (x <= 0) {
@@ -243,13 +243,13 @@ public class TokenAi extends SpellAbilityAi {
final int nToSac = AbilityUtils.calculateAmount(topStack.getHostCard(), num, topStack);
CardCollection list = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
ai.getWeakestOpponent(), topStack.getHostCard(), sa);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true));
// only care about saving single creature for now
if (!list.isEmpty() && nTokens > 0 && list.size() == nToSac) {
ComputerUtilCard.sortByEvaluateCreature(list);
list.add(token);
list = CardLists.getValidCards(list, valid.split(","), ai.getWeakestOpponent(), topStack.getHostCard(), sa);
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack));
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(topStack, true));
return ComputerUtilCard.evaluateCreature(token) < ComputerUtilCard.evaluateCreature(list.get(0))
&& list.contains(token);
}
@@ -284,7 +284,7 @@ public class TokenAi extends SpellAbilityAi {
if (sa.getSVar("X").equals("Count$xPaid")) {
if (x == 0) { // already paid outside trigger
// Set PayX here to maximum value.
x = ComputerUtilCost.getMaxXValue(sa, ai);
x = ComputerUtilCost.getMaxXValue(sa, ai, true);
sa.setXManaCostPaid(x);
}
}

View File

@@ -30,7 +30,7 @@ public class UnattachAllAi extends SpellAbilityAi {
}
if (sa.getSVar("X").equals("Count$xPaid")) {
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger());
if (xPay == 0) {
return false;

View File

@@ -367,7 +367,7 @@ public class UntapAi extends SpellAbilityAi {
// can ideally be improved to work by color.
ManaCostBeingPaid reduced = new ManaCostBeingPaid(ab.getPayCosts().getCostMana().getManaCostFor(ab), ab.getPayCosts().getCostMana().getRestriction());
reduced.decreaseShard(ManaCostShard.GENERIC, untappingCards.size());
if (ComputerUtilMana.canPayManaCost(reduced, ab, ai)) {
if (ComputerUtilMana.canPayManaCost(reduced, ab, ai, false)) {
CardCollection manaLandsTapped = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
Predicates.and(Presets.LANDS_PRODUCING_MANA, Presets.TAPPED));
manaLandsTapped = CardLists.filter(manaLandsTapped, new Predicate<Card>() {
@@ -400,7 +400,7 @@ public class UntapAi extends SpellAbilityAi {
Card landToPool = manaLands.getFirst();
SpellAbility manaAb = landToPool.getManaAbilities().getFirst();
ComputerUtil.playNoStack(ai, manaAb, game);
ComputerUtil.playNoStack(ai, manaAb, game, false);
return true;
}

View File

@@ -225,7 +225,7 @@ public class SpellAbilityChoicesIterator {
// TODO this should also iterate over all possible values
// (currently no additional complexity to keep performance reasonable)
if (sa.costHasManaX()) {
Integer x = ComputerUtilCost.getMaxXValue(sa, sa.getActivatingPlayer());
Integer x = ComputerUtilCost.getMaxXValue(sa, sa.getActivatingPlayer(), sa.isTrigger());
sa.setXManaCostPaid(x);
controller.getLastDecision().xMana = x;
}

View File

@@ -156,7 +156,7 @@ public class SpellAbilityPicker {
if (sa.isPwAbility()) {
return !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed.");
}
return sa.isAbility() && sa.getRestrictions().isSorcerySpeed();
return sa.isActivatedAbility() && sa.getRestrictions().isSorcerySpeed();
}
private void createNewPlan(Score origGameScore, List<SpellAbility> candidateSAs) {
@@ -348,7 +348,7 @@ public class SpellAbilityPicker {
return AiPlayDecision.CantPlaySa;
}
if (!ComputerUtilCost.canPayCost(sa, player)) {
if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) {
return AiPlayDecision.CantAfford;
}
@@ -434,11 +434,11 @@ public class SpellAbilityPicker {
}
}
public CardCollectionView chooseSacrificeType(String type, SpellAbility ability, int amount, final CardCollectionView exclude) {
public CardCollectionView chooseSacrificeType(String type, SpellAbility ability, final boolean effect, int amount, final CardCollectionView exclude) {
if (amount == 1) {
Card source = ability.getHostCard();
CardCollection cardList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, null);
cardList = CardLists.filter(cardList, CardPredicates.canBeSacrificedBy(ability));
cardList = CardLists.filter(cardList, CardPredicates.canBeSacrificedBy(ability, effect));
if (cardList.size() >= 2) {
if (interceptor != null) {
return new CardCollection(interceptor.chooseCard(cardList));
@@ -450,7 +450,7 @@ public class SpellAbilityPicker {
}
}
}
return ComputerUtil.chooseSacrificeType(player, type, ability, ability.getTargetCard(), amount, exclude);
return ComputerUtil.chooseSacrificeType(player, type, ability, ability.getTargetCard(), effect, amount, exclude);
}
public static class PlayLandAbility extends LandAbility {

View File

@@ -93,14 +93,14 @@ public class ForgeScript {
return !cardState.getTypeWithChanges().hasSubtype(subType);
} else if (property.equals("hasActivatedAbilityWithTapCost")) {
for (final SpellAbility sa : cardState.getSpellAbilities()) {
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
if (sa.isActivatedAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}
return false;
} else if (property.equals("hasActivatedAbility")) {
for (final SpellAbility sa : cardState.getSpellAbilities()) {
if (sa.isAbility()) {
if (sa.isActivatedAbility()) {
return true;
}
}
@@ -116,8 +116,8 @@ public class ForgeScript {
}
return false;
} else if (property.equals("hasNonManaActivatedAbility")) {
for (final SpellAbility sa : cardState.getSpellAbilities()) {
if (sa.isAbility() && !sa.isManaAbility()) {
for (final SpellAbility sa : cardState.getNonManaAbilities()) {
if (sa.isActivatedAbility()) {
return true;
}
}

View File

@@ -1452,14 +1452,15 @@ public class GameAction {
if (!c.getType().hasSubtype("Saga")) {
return false;
}
if (!c.canBeSacrificed()) {
if (!c.canBeSacrificedBy(null, true)) {
return false;
}
if (c.getCounters(CounterEnumType.LORE) < c.getFinalChapterNr()) {
return false;
}
if (!game.getStack().hasSourceOnStack(c, SpellAbilityPredicates.isChapter())) {
sacrifice(c, null, table, null);
// needs to be effect, because otherwise it might be a cost?
sacrifice(c, null, true, table, null);
checkAgain = true;
}
return checkAgain;
@@ -1759,8 +1760,8 @@ public class GameAction {
return true;
}
public final Card sacrifice(final Card c, final SpellAbility source, CardZoneTable table, Map<AbilityKey, Object> params) {
if (!c.canBeSacrificedBy(source)) {
public final Card sacrifice(final Card c, final SpellAbility source, final boolean effect, CardZoneTable table, Map<AbilityKey, Object> params) {
if (!c.canBeSacrificedBy(source, effect)) {
return null;
}

View File

@@ -682,13 +682,13 @@ public abstract class SpellAbilityEffect {
};
}
protected static void discard(SpellAbility sa, CardZoneTable table, Map<Player, CardCollectionView> discardedMap) {
protected static void discard(SpellAbility sa, CardZoneTable table, final boolean effect, Map<Player, CardCollectionView> discardedMap) {
Set<Player> discarders = discardedMap.keySet();
for (Player p : discarders) {
final CardCollection discardedByPlayer = new CardCollection();
for (Card card : Lists.newArrayList(discardedMap.get(p))) { // without copying will get concurrent modification exception
if (card == null) { continue; }
if (p.discard(card, sa, table) != null) {
if (p.discard(card, sa, effect, table) != null) {
discardedByPlayer.add(card);
if (sa.hasParam("RememberDiscarded")) {

View File

@@ -61,13 +61,13 @@ public class BalanceEffect extends SpellAbilityEffect {
} else { // Battlefield
for (Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) {
if ( null == card ) continue;
game.getAction().sacrifice(card, sa, table, params);
game.getAction().sacrifice(card, sa, true, table, params);
}
}
}
if (zone.equals(ZoneType.Hand)) {
discard(sa, table, discardedMap);
discard(sa, table, true, discardedMap);
}
table.triggerChangesZoneAll(game, sa);

View File

@@ -9,6 +9,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.cost.Cost;
import forge.game.event.GameEventCardModeChosen;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -52,18 +53,11 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
if (!saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer()) ) {
saToRemove.add(saChoice);
} else if (saChoice.hasParam("UnlessCost")) {
String unlessCost = saChoice.getParam("UnlessCost");
// Sac a permanent in presence of Sigarda, Host of Herons
// TODO: generalize this by testing if the unless cost can be paid
if (unlessCost.startsWith("Sac<")) {
if (!p.canSacrificeBy(saChoice)) {
// generic check for if the cost can be paid
Cost unlessCost = new Cost(saChoice.getParam("UnlessCost"), false);
if (!unlessCost.canPay(sa, p, true)) {
saToRemove.add(saChoice);
}
} else if (unlessCost.startsWith("Discard<")) {
if (!p.canDiscardBy(sa)) {
saToRemove.add(saChoice);
}
}
}
}
abilities.removeAll(saToRemove);

View File

@@ -126,7 +126,7 @@ public class DestroyEffect extends SpellAbilityEffect {
card.addRemembered(gameCard.getAttachedCards());
}
if (sac) {
destroyed = game.getAction().sacrifice(gameCard, sa, table, params) != null;
destroyed = game.getAction().sacrifice(gameCard, sa, true, table, params) != null;
} else {
destroyed = game.getAction().destroy(gameCard, sa, !noRegen, table, params);
}

View File

@@ -34,7 +34,7 @@ public class DiscardEffect extends SpellAbilityEffect {
final String mode = sa.getParam("Mode");
final StringBuilder sb = new StringBuilder();
final Iterable<Player> tgtPlayers = Iterables.filter(getTargetPlayers(sa), PlayerPredicates.canDiscardBy(sa));
final Iterable<Player> tgtPlayers = Iterables.filter(getTargetPlayers(sa), PlayerPredicates.canDiscardBy(sa, true));
if (!Iterables.isEmpty(tgtPlayers)) {
sb.append(Lang.joinHomogenous(tgtPlayers)).append(" ");
@@ -127,12 +127,12 @@ public class DiscardEffect extends SpellAbilityEffect {
for (final Player p : discarders) {
CardCollectionView toBeDiscarded = new CardCollection();
if ((mode.equals("RevealTgtChoose") && firstTarget != null) || !sa.usesTargeting() || p.canBeTargetedBy(sa)) {
if (sa.hasParam("RememberDiscarder") && p.canDiscardBy(sa)) {
if (sa.hasParam("RememberDiscarder") && p.canDiscardBy(sa, true)) {
source.addRemembered(p);
}
final int numCardsInHand = p.getCardsIn(ZoneType.Hand).size();
if (mode.equals("Defined")) {
if (!p.canDiscardBy(sa)) {
if (!p.canDiscardBy(sa, true)) {
continue;
}
@@ -148,10 +148,12 @@ public class DiscardEffect extends SpellAbilityEffect {
}
if (mode.equals("Hand")) {
if (!p.canDiscardBy(sa)) {
toBeDiscarded = p.getCardsIn(ZoneType.Hand);
// Empty hand can still be discarded
if (!toBeDiscarded.isEmpty() && !p.canDiscardBy(sa, true)) {
continue;
}
toBeDiscarded = p.getCardsIn(ZoneType.Hand);
if (toBeDiscarded.size() > 1) {
toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa);
@@ -159,7 +161,7 @@ public class DiscardEffect extends SpellAbilityEffect {
}
if (mode.equals("NotRemembered")) {
if (!p.canDiscardBy(sa)) {
if (!p.canDiscardBy(sa, true)) {
continue;
}
toBeDiscarded = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), "Card.IsNotRemembered", p, source, sa);
@@ -175,7 +177,7 @@ public class DiscardEffect extends SpellAbilityEffect {
}
if (mode.equals("Random")) {
if (!p.canDiscardBy(sa)) {
if (!p.canDiscardBy(sa, true)) {
continue;
}
String message = Localizer.getInstance().getMessage("lblWouldYouLikeRandomDiscardTargetCard", String.valueOf(numCards));
@@ -202,7 +204,7 @@ public class DiscardEffect extends SpellAbilityEffect {
}
}
else if (mode.equals("TgtChoose") && sa.hasParam("UnlessType")) {
if (!p.canDiscardBy(sa)) {
if (!p.canDiscardBy(sa, true)) {
continue;
}
if (numCardsInHand > 0) {
@@ -223,7 +225,7 @@ public class DiscardEffect extends SpellAbilityEffect {
opp.getController().reveal(dPHand, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblReveal") + " ");
}
if (!p.canDiscardBy(sa)) {
if (!p.canDiscardBy(sa, true)) {
continue;
}
@@ -265,7 +267,7 @@ public class DiscardEffect extends SpellAbilityEffect {
game.getAction().reveal(dPHand, p);
}
if (!p.canDiscardBy(sa)) {
if (!p.canDiscardBy(sa, true)) {
continue;
}
@@ -286,7 +288,7 @@ public class DiscardEffect extends SpellAbilityEffect {
discardedMap.put(p, toBeDiscarded);
}
discard(sa, table, discardedMap);
discard(sa, table, true, discardedMap);
// run trigger if something got milled
table.triggerChangesZoneAll(game, sa);

View File

@@ -68,7 +68,7 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
// gameCard is LKI in that case, the card is not in game anymore
// or the timestamp did change
// this should check Self too
if (gameCard == null || !sac.equalsWithTimestamp(gameCard) || !gameCard.canBeSacrificedBy(sa)) {
if (gameCard == null || !sac.equalsWithTimestamp(gameCard) || !gameCard.canBeSacrificedBy(sa, true)) {
continue;
}
gameList.add(gameCard);
@@ -92,7 +92,7 @@ public class SacrificeAllEffect extends SpellAbilityEffect {
for (Card sac : list) {
final Card lKICopy = CardUtil.getLKICopy(sac, cachedMap);
if (game.getAction().sacrifice(sac, sa, table, params) != null) {
if (game.getAction().sacrifice(sac, sa, true, table, params) != null) {
if (remSacrificed) {
card.addRemembered(lKICopy);
}

View File

@@ -109,7 +109,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
if (game.getZoneOf(card).is(ZoneType.Battlefield)) {
if (!optional || activator.getController().confirmAction(sa, null,
Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", card.getName()))) {
if (game.getAction().sacrifice(card, sa, table, params) != null) {
if (game.getAction().sacrifice(card, sa, true, table, params) != null) {
if (remSacrificed) {
card.addRemembered(card);
}
@@ -126,7 +126,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
List<CardCollection> validTargetsList = new ArrayList<>(validArray.length);
for (String subValid : validArray) {
CardCollectionView validTargets = AbilityUtils.filterListByType(battlefield, subValid, sa);
validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa));
validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa, true));
validTargetsList.add(new CardCollection(validTargets));
}
CardCollection chosenCards = new CardCollection();
@@ -146,7 +146,7 @@ public class SacrificeEffect extends SpellAbilityEffect {
} else {
CardCollectionView validTargets = AbilityUtils.filterListByType(battlefield, valid, sa);
if (!destroy) {
validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa));
validTargets = CardLists.filter(validTargets, CardPredicates.canBeSacrificedBy(sa, true));
}
if (sa.hasParam("Random")) {
@@ -175,13 +175,13 @@ public class SacrificeEffect extends SpellAbilityEffect {
Map<Integer, Card> cachedMap = Maps.newHashMap();
for (Card sac : choosenToSacrifice) {
final Card lKICopy = CardUtil.getLKICopy(sac, cachedMap);
boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa, table, params) != null;
boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa, true, table, params) != null;
boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa, true, table, params);
// Run Devour Trigger
if (devour) {
card.addDevoured(lKICopy);
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
runParams.put(AbilityKey.Devoured, sac);
runParams.put(AbilityKey.Devoured, lKICopy);
game.getTriggerHandler().runTrigger(TriggerType.Devoured, runParams, false);
}
if (exploit) {

View File

@@ -43,7 +43,6 @@ import forge.game.ability.ApiType;
import forge.game.combat.Combat;
import forge.game.combat.CombatLki;
import forge.game.cost.Cost;
import forge.game.cost.CostSacrifice;
import forge.game.event.*;
import forge.game.event.GameEventCardDamaged.DamageType;
import forge.game.keyword.*;
@@ -57,6 +56,7 @@ import forge.game.spellability.*;
import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.staticability.StaticAbilityCantPutCounter;
import forge.game.staticability.StaticAbilityCantSacrifice;
import forge.game.staticability.StaticAbilityCantTransform;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
@@ -5946,10 +5946,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return isInPlay() && !isPhasedOut() && (!hasKeyword(Keyword.INDESTRUCTIBLE) || (isCreature() && getNetToughness() <= 0));
}
public final boolean canBeSacrificed() {
return isInPlay() && !isPhasedOut() && !hasKeyword("CARDNAME can't be sacrificed.");
}
@Override
public final boolean canBeTargetedBy(final SpellAbility sa) {
if (getOwner().hasLost()) {
@@ -6284,29 +6280,16 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return this.lkiCMC >= 0;
}
public final boolean canBeSacrificedBy(final SpellAbility source) {
public final boolean canBeSacrificedBy(final SpellAbility source, final boolean effect) {
if (isImmutable()) {
System.out.println("Trying to sacrifice immutables: " + this);
return false;
}
if (!canBeSacrificed()) {
if (!isInPlay() || isPhasedOut()) {
return false;
}
if (source == null) {
return true;
}
if ((source.isSpell() || source.isActivatedAbility()) && source.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
if (isCreature() && source.getActivatingPlayer().hasKeyword("You can't sacrifice creatures to cast spells or activate abilities.")) {
return false;
}
if (isPermanent() && !isLand() && source.getActivatingPlayer().hasKeyword("You can't sacrifice nonland permanents to cast spells or activate abilities.")) {
return false;
}
}
return getController().canSacrificeBy(source);
return !StaticAbilityCantSacrifice.cantSacrifice(this, source, effect);
}
public CardRules getRules() {
@@ -6828,12 +6811,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
return n;
}
public boolean canBeDiscardedBy(SpellAbility sa) {
public boolean canBeDiscardedBy(SpellAbility sa, final boolean effect) {
if (!isInZone(ZoneType.Hand)) {
return false;
}
return getOwner().canDiscardBy(sa);
return getOwner().canDiscardBy(sa, effect);
}
public void addAbilityActivated(SpellAbility ability) {

View File

@@ -251,11 +251,11 @@ public final class CardPredicates {
};
}
public static final Predicate<Card> canBeSacrificedBy(final SpellAbility sa) {
public static final Predicate<Card> canBeSacrificedBy(final SpellAbility sa, final boolean effect) {
return new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.canBeSacrificedBy(sa);
return c.canBeSacrificedBy(sa, effect);
}
};
}

View File

@@ -392,7 +392,8 @@ public class CardProperty {
}
}
} else if (property.equals("CanBeSacrificedBy") && spellAbility instanceof SpellAbility) {
if (!card.canBeSacrificedBy((SpellAbility) spellAbility)) {
// used for Emerge and Offering, these are SpellCost, not effect
if (!card.canBeSacrificedBy((SpellAbility) spellAbility, false)) {
return false;
}
} else if (property.startsWith("AttachedBy")) {

View File

@@ -939,12 +939,12 @@ public class Cost implements Serializable {
}
}
public boolean canPay(SpellAbility sa) {
return canPay(sa, sa.getActivatingPlayer());
public boolean canPay(SpellAbility sa, final boolean effect) {
return canPay(sa, sa.getActivatingPlayer(), effect);
}
public boolean canPay(SpellAbility sa, Player payer) {
public boolean canPay(SpellAbility sa, Player payer, final boolean effect) {
for (final CostPart part : this.getCostParts()) {
if (!part.canPay(sa, payer)) {
if (!part.canPay(sa, payer, effect)) {
return false;
}
}
@@ -968,14 +968,14 @@ public class Cost implements Serializable {
return xCost;
}
public Integer getMaxForNonManaX(final SpellAbility ability, final Player payer) {
public Integer getMaxForNonManaX(final SpellAbility ability, final Player payer, final boolean effect) {
Integer val = null;
for (CostPart p : getCostParts()) {
if (!p.getAmount().equals("X")) {
continue;
}
val = ObjectUtils.min(val, p.getMaxAmountX(ability, payer));
val = ObjectUtils.min(val, p.getMaxAmountX(ability, payer, effect));
}
// extra 0 check
if (val != null && val <= 0 && hasManaCost() && !getCostMana().canXbe0()) {

View File

@@ -68,12 +68,12 @@ public class CostAddMana extends CostPart {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
return true;
}
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility sa) {
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility sa, final boolean effect) {
Card source = sa.getHostCard();
List<Mana> manaProduced = new ArrayList<>();

View File

@@ -292,7 +292,7 @@ public class CostAdjustment {
Card toSac = null;
CardCollectionView canOffer = CardLists.filter(sa.getActivatingPlayer().getCardsIn(ZoneType.Battlefield),
CardPredicates.isType(offeringType), CardPredicates.canBeSacrificedBy(sa));
CardPredicates.isType(offeringType), CardPredicates.canBeSacrificedBy(sa, false));
final CardCollectionView toSacList = sa.getHostCard().getController().getController().choosePermanentsToSacrifice(sa, 0, 1, canOffer, offeringType);
@@ -309,7 +309,7 @@ public class CostAdjustment {
private static void adjustCostByEmerge(final ManaCostBeingPaid cost, final SpellAbility sa) {
Card toSac = null;
CardCollectionView canEmerge = CardLists.filter(sa.getActivatingPlayer().getCreaturesInPlay(), CardPredicates.canBeSacrificedBy(sa));
CardCollectionView canEmerge = CardLists.filter(sa.getActivatingPlayer().getCreaturesInPlay(), CardPredicates.canBeSacrificedBy(sa, false));
final CardCollectionView toSacList = sa.getHostCard().getController().getController().choosePermanentsToSacrifice(sa, 0, 1, canEmerge, "Creature");

View File

@@ -48,12 +48,12 @@ public class CostChooseCreatureType extends CostPart {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
return true;
}
@Override
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa) {
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa, final boolean effect) {
sa.getHostCard().setChosenType(pd.type);
return true;
}

View File

@@ -60,12 +60,12 @@ public class CostDamage extends CostPart {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
return true;
}
@Override
public boolean payAsDecided(Player payer, PaymentDecision decision, SpellAbility sa) {
public boolean payAsDecided(Player payer, PaymentDecision decision, SpellAbility sa, final boolean effect) {
final Card source = sa.getHostCard();
CardDamageMap damageMap = new CardDamageMap();
CardDamageMap preventMap = new CardDamageMap();

View File

@@ -5,9 +5,14 @@ import forge.game.player.Player;
public abstract class CostDecisionMakerBase implements ICostVisitor<PaymentDecision> {
protected final Player player;
public CostDecisionMakerBase(Player player0) {
private boolean effect;
public CostDecisionMakerBase(Player player0, boolean effect0) {
player = player0;
effect = effect0;
}
public Player getPlayer() { return player; }
public abstract boolean paysRightAfterDecision();
public boolean isEffect() {
return effect;
}
}

View File

@@ -44,9 +44,6 @@ public class CostDiscard extends CostPartWithList {
protected boolean firstTime = false;
/**
* Serializables need a version ID.
*/
private static final long serialVersionUID = 1L;
/**
@@ -66,10 +63,10 @@ public class CostDiscard extends CostPartWithList {
public int paymentOrder() { return 10; }
@Override
public Integer getMaxAmountX(SpellAbility ability, Player payer) {
public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) {
final Card source = ability.getHostCard();
String type = this.getType();
CardCollectionView handList = payer.canDiscardBy(ability) ? payer.getCardsIn(ZoneType.Hand) : CardCollection.EMPTY;
CardCollectionView handList = payer.canDiscardBy(ability, effect) ? payer.getCardsIn(ZoneType.Hand) : CardCollection.EMPTY;
if (!type.equals("Random")) {
handList = CardLists.getValidCards(handList, type.split(";"), payer, source, ability);
@@ -128,19 +125,23 @@ public class CostDiscard extends CostPartWithList {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
CardCollectionView handList = payer.canDiscardBy(ability) ? payer.getCardsIn(ZoneType.Hand) : CardCollection.EMPTY;
CardCollectionView handList = payer.canDiscardBy(ability, effect) ? payer.getCardsIn(ZoneType.Hand) : CardCollection.EMPTY;
String type = this.getType();
final Integer amount = this.convertAmount();
final int amount = getAbilityAmount(ability);
if (this.payCostFromSource()) {
return source.canBeDiscardedBy(ability);
return source.canBeDiscardedBy(ability, effect);
}
else {
if (type.equals("Hand")) {
return payer.canDiscardBy(ability);
// trying to discard an empty hand always work even with Tamiyo
if (payer.getZone(ZoneType.Hand).isEmpty()) {
return true;
}
return payer.canDiscardBy(ability, effect);
// this will always work
}
else if (type.equals("LastDrawn")) {
@@ -152,7 +153,7 @@ public class CostDiscard extends CostPartWithList {
for (Card c : handList) {
cardNames.add(c.getName());
}
return amount != null && cardNames.size() >= amount;
return cardNames.size() >= amount;
}
else {
boolean sameName = false;
@@ -180,7 +181,7 @@ public class CostDiscard extends CostPartWithList {
}
}
if ((amount != null) && (amount > handList.size() - adjustment)) {
if (amount > handList.size() - adjustment) {
// not enough cards in hand to pay
return false;
}
@@ -193,7 +194,7 @@ public class CostDiscard extends CostPartWithList {
* @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
if (ability.isCycling() && targetCard.equals(ability.getHostCard())) {
// discard itself for cycling cost
@@ -201,7 +202,7 @@ public class CostDiscard extends CostPartWithList {
}
// if this is caused by 118.12 it's also an effect
SpellAbility cause = targetCard.getGame().getStack().isResolving(ability.getHostCard()) ? ability : null;
return targetCard.getController().discard(targetCard, cause, null, runParams);
return targetCard.getController().discard(targetCard, cause, effect, null, runParams);
}
/* (non-Javadoc)

View File

@@ -17,7 +17,6 @@
*/
package forge.game.cost;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
@@ -27,9 +26,7 @@ import forge.game.spellability.SpellAbility;
* The Class CostDraw.
*/
public class CostDraw extends CostPart {
/**
* Serializables need a version ID.
*/
private static final long serialVersionUID = 1L;
/**
@@ -66,10 +63,8 @@ public class CostDraw extends CostPart {
PlayerCollection res = new PlayerCollection();
String type = this.getType();
final Card source = ability.getHostCard();
Integer c = convertAmount();
if (c == null) {
c = AbilityUtils.calculateAmount(source, getAmount(), ability);
}
int c = this.getAbilityAmount(ability);
for (Player p : payer.getGame().getPlayers()) {
if (p.isValid(type, payer, source, ability) && p.canDrawAmount(c)) {
@@ -87,7 +82,7 @@ public class CostDraw extends CostPart {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
return !getPotentialPlayers(payer, ability).isEmpty();
}
@@ -98,7 +93,7 @@ public class CostDraw extends CostPart {
* forge.Card, forge.card.cost.Cost_Payment)
*/
@Override
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability) {
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability, final boolean effect) {
for (final Player p : decision.players) {
p.drawCards(decision.c, ability);
}

View File

@@ -29,9 +29,6 @@ import forge.game.zone.ZoneType;
*/
public class CostExert extends CostPartWithList {
/**
* Serializables need a version ID.
*/
private static final long serialVersionUID = 1L;
/**
@@ -81,7 +78,7 @@ public class CostExert extends CostPartWithList {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
if (!this.payCostFromSource()) {
@@ -89,17 +86,17 @@ public class CostExert extends CostPartWithList {
CardCollectionView typeList = payer.getCardsIn(ZoneType.Battlefield);
typeList = CardLists.getValidCards(typeList, this.getType().split(";"), payer, source, ability);
final Integer amount = this.convertAmount();
final int amount = this.getAbilityAmount(ability);
return needsAnnoucement || (amount == null) || (typeList.size() >= amount);
return needsAnnoucement || (typeList.size() >= amount);
}
return true;
}
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
targetCard.exert();
return targetCard;
}

View File

@@ -18,7 +18,6 @@
package forge.game.cost;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
@@ -61,7 +60,7 @@ public class CostExile extends CostPartWithList {
}
@Override
public Integer getMaxAmountX(SpellAbility ability, Player payer) {
public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) {
final Card source = ability.getHostCard();
final Game game = source.getGame();
@@ -126,7 +125,7 @@ public class CostExile extends CostPartWithList {
}
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
final Game game = source.getGame();
@@ -153,22 +152,18 @@ public class CostExile extends CostPartWithList {
list = CardLists.getValidCards(list, type.split(";"), payer, source, ability);
}
Integer amount = this.convertAmount();
if (amount == null) { // try to calculate when it's defined.
amount = AbilityUtils.calculateAmount(ability.getHostCard(), getAmount(), ability);
}
int amount = this.getAbilityAmount(ability);
// for cards like Allosaurus Rider, do not count it
if (this.from == ZoneType.Hand && source.isInZone(ZoneType.Hand) && list.contains(source)) {
amount++;
}
if (amount != null && list.size() < amount) {
if (list.size() < amount) {
return false;
}
if (this.sameZone && amount != null) {
if (this.sameZone) {
boolean foundPayable = false;
FCollectionView<Player> players = game.getPlayers();
for (Player p : players) {
@@ -183,7 +178,7 @@ public class CostExile extends CostPartWithList {
}
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
final Game game = targetCard.getGame();
Card newCard = game.getAction().exile(targetCard, null);
newCard.setExiledWith(ability.getHostCard());

View File

@@ -32,9 +32,6 @@ import forge.game.zone.ZoneType;
public class CostExileFromStack extends CostPart {
// ExileFromStack<Num/Type{/TypeDescription}>
/**
* Serializables need a version ID.
*/
private static final long serialVersionUID = 1L;
/**
@@ -81,7 +78,7 @@ public class CostExileFromStack extends CostPart {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
String type = this.getType();
@@ -93,8 +90,8 @@ public class CostExileFromStack extends CostPart {
list = CardLists.getValidCards(list, type.split(";"), payer, source, ability);
final Integer amount = this.convertAmount();
return (amount == null) || (list.size() >= amount);
final int amount = this.getAbilityAmount(ability);
return list.size() >= amount;
}
/*
@@ -104,7 +101,7 @@ public class CostExileFromStack extends CostPart {
* forge.Card, forge.card.cost.Cost_Payment)
*/
@Override
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability) {
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability, final boolean effect) {
Game game = ai.getGame();
for (final SpellAbility sa : decision.sp) {
SpellAbilityStackInstance si = game.getStack().getInstanceFromSpellAbility(sa);

View File

@@ -17,7 +17,6 @@
*/
package forge.game.cost;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
@@ -43,7 +42,7 @@ public class CostExiledMoveToGrave extends CostPartWithList {
public int paymentOrder() { return 15; }
@Override
public Integer getMaxAmountX(SpellAbility ability, Player payer) {
public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) {
final Card source = ability.getHostCard();
CardCollectionView typeList = payer.getGame().getCardsIn(ZoneType.Exile);
@@ -76,20 +75,15 @@ public class CostExiledMoveToGrave extends CostPartWithList {
}
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
final Card source = ability.getHostCard();
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
Integer i = convertAmount();
int i = getAbilityAmount(ability);
if (i == null) {
i = AbilityUtils.calculateAmount(source, getAmount(), ability);
}
return getMaxAmountX(ability, payer) >= i;
return getMaxAmountX(ability, payer, effect) >= i;
}
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
return targetCard.getGame().getAction().moveToGraveyard(targetCard, null);
}

View File

@@ -49,7 +49,7 @@ public class CostFlipCoin extends CostPart {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
return true;
}
@@ -59,7 +59,7 @@ public class CostFlipCoin extends CostPart {
}
@Override
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa) {
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa, final boolean effect) {
int m = FlipCoinEffect.getFlipMultiplier(payer);
for (int i = 0; i < pd.c; i++) {
FlipCoinEffect.flipCoinCall(payer, sa, m);

View File

@@ -17,7 +17,6 @@
*/
package forge.game.cost;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
@@ -74,23 +73,19 @@ public class CostGainControl extends CostPartWithList {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
CardCollectionView typeList = payer.getGame().getCardsIn(ZoneType.Battlefield);
typeList = CardLists.getValidCards(typeList, this.getType().split(";"), payer, source, ability);
Integer amount = this.convertAmount();
if (amount == null) {
amount = AbilityUtils.calculateAmount(source, this.getAmount(), ability);
}
return typeList.size() >= amount;
return typeList.size() >= getAbilityAmount(ability);
}
/* (non-Javadoc)
* @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
targetCard.addTempController(ability.getActivatingPlayer(), ability.getActivatingPlayer().getGame().getNextTimestamp());
return targetCard;
}

View File

@@ -17,10 +17,10 @@
*/
package forge.game.cost;
import java.util.ArrayList;
import java.util.List;
import forge.game.card.Card;
import com.google.common.collect.Lists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -28,9 +28,7 @@ import forge.game.spellability.SpellAbility;
* The Class CostGainLife.
*/
public class CostGainLife extends CostPart {
/**
* Serializables need a version ID.
*/
private static final long serialVersionUID = 1L;
private final int cntPlayers; // MAX_VALUE means ALL/EACH PLAYERS
@@ -64,29 +62,19 @@ public class CostGainLife extends CostPart {
return sb.toString();
}
public List<Player> getPotentialTargets(final Player payer, final Card source) {
List<Player> res = new ArrayList<>();
public List<Player> getPotentialTargets(final Player payer, final SpellAbility ability) {
List<Player> res = Lists.newArrayList();
for (Player p : payer.getGame().getPlayers()) {
if (p.isValid(getType(), payer, source, null))
if (p.isValid(getType(), payer, ability.getHostCard(), ability))
res.add(p);
}
return res;
}
/*
* (non-Javadoc)
*
* @see
* forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
final Integer amount = this.convertAmount();
if (amount == null) return false;
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
int cntAbleToGainLife = 0;
List<Player> possibleTargets = getPotentialTargets(payer, ability.getHostCard());
List<Player> possibleTargets = getPotentialTargets(payer, ability);
for (final Player opp : possibleTargets) {
if (opp.canGainLife()) {
@@ -97,15 +85,9 @@ public class CostGainLife extends CostPart {
return cntAbleToGainLife >= cntPlayers || cntPlayers == Integer.MAX_VALUE && cntAbleToGainLife == possibleTargets.size();
}
/*
* (non-Javadoc)
*
* @see forge.card.cost.CostPart#payAI(forge.card.spellability.SpellAbility,
* forge.Card, forge.card.cost.Cost_Payment)
*/
@Override
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability) {
Integer c = this.convertAmount();
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability, final boolean effect) {
Integer c = this.getAbilityAmount(ability);
int playersLeft = cntPlayers;
for (final Player opp : decision.players) {

View File

@@ -17,13 +17,10 @@
*/
package forge.game.cost;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
/**
@@ -59,17 +56,8 @@ public class CostMill extends CostPart {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
final Card source = ability.getHostCard();
final PlayerZone zone = payer.getZone(ZoneType.Library);
Integer i = this.convertAmount();
if (i == null) {
i = AbilityUtils.calculateAmount(source, this.getAmount(), ability);
}
return i < zone.size();
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
return getAbilityAmount(ability) < payer.getZone(ZoneType.Library).size();
}
/*
@@ -98,7 +86,7 @@ public class CostMill extends CostPart {
}
@Override
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability) {
public final boolean payAsDecided(final Player ai, final PaymentDecision decision, SpellAbility ability, final boolean effect) {
CardZoneTable table = new CardZoneTable();
ability.getPaidHash().put("Milled", (CardCollection) ai.mill(decision.c, ZoneType.Graveyard, false, ability, table));
table.triggerChangesZoneAll(ai.getGame(), ability);

View File

@@ -74,7 +74,7 @@ public abstract class CostPart implements Comparable<CostPart>, Cloneable, Seria
return this.amount;
}
public Integer getMaxAmountX(final SpellAbility ability, final Player payer) {
public Integer getMaxAmountX(final SpellAbility ability, final Player payer, final boolean effect) {
return null;
}
/**
@@ -145,6 +145,10 @@ public abstract class CostPart implements Comparable<CostPart>, Cloneable, Seria
return StringUtils.isNumeric(amount) ? Integer.parseInt(amount) : null;
}
public final int getAbilityAmount(SpellAbility ability) {
return AbilityUtils.calculateAmount(ability.getHostCard(), getAmount(), ability);
}
/**
* Can pay.
*
@@ -153,7 +157,7 @@ public abstract class CostPart implements Comparable<CostPart>, Cloneable, Seria
* @param payer
* @return true, if successful
*/
public abstract boolean canPay(SpellAbility ability, Player payer);
public abstract boolean canPay(SpellAbility ability, Player payer, boolean effect);
public abstract <T> T accept(final ICostVisitor<T> visitor);
@@ -191,7 +195,7 @@ public abstract class CostPart implements Comparable<CostPart>, Cloneable, Seria
this.typeDescription = AbilityUtils.applyDescriptionTextChangeEffects(this.originalTypeDescription, trait);
}
public abstract boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa);
public abstract boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa, final boolean effect);
public int paymentOrder() { return 5; }

View File

@@ -122,7 +122,7 @@ public class CostPartMana extends CostPart {
}
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
// For now, this will always return true. But this should probably be
// checked at some point
return true;
@@ -170,11 +170,11 @@ public class CostPartMana extends CostPart {
}
@Override
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa) {
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa, final boolean effect) {
sa.clearManaPaid();
// decision not used here, the whole payment is interactive!
return payer.getController().payManaCost(this, sa, null, cardMatrix, true);
return payer.getController().payManaCost(this, sa, null, cardMatrix, effect);
}
}

View File

@@ -101,10 +101,10 @@ public abstract class CostPartWithList extends CostPart {
super(amount, type, description);
}
public final boolean executePayment(SpellAbility ability, Card targetCard) {
public final boolean executePayment(SpellAbility ability, Card targetCard, final boolean effect) {
lkiList.add(CardUtil.getLKICopy(targetCard));
final Zone origin = targetCard.getZone();
final Card newCard = doPayment(ability, targetCard);
final Card newCard = doPayment(ability, targetCard, effect);
// need to update the LKI info to ensure correct interaction with cards which may trigger on this
// (e.g. Necroskitter + a creature dying from a -1/-1 counter on a cost payment).
@@ -122,16 +122,16 @@ public abstract class CostPartWithList extends CostPart {
}
// always returns true, made this to inline with return
protected boolean executePayment(Player payer, SpellAbility ability, CardCollectionView targetCards) {
protected boolean executePayment(Player payer, SpellAbility ability, CardCollectionView targetCards, final boolean effect) {
handleBeforePayment(payer, ability, targetCards);
if (canPayListAtOnce()) { // This is used by reveal. Without it when opponent would reveal hand, you'll get N message boxes.
for (Card c: targetCards) {
lkiList.add(CardUtil.getLKICopy(c));
}
cardList.addAll(doListPayment(ability, targetCards));
cardList.addAll(doListPayment(ability, targetCards, effect));
} else {
for (Card c : targetCards) {
executePayment(ability, c);
executePayment(ability, c, effect);
}
}
handleChangeZoneTrigger(payer, ability, targetCards);
@@ -144,10 +144,10 @@ public abstract class CostPartWithList extends CostPart {
* @param targetCard the {@link Card} to pay with.
* @return The physical card after the payment.
*/
protected abstract Card doPayment(SpellAbility ability, Card targetCard);
protected abstract Card doPayment(SpellAbility ability, Card targetCard, final boolean effect);
// Overload these two only together, set to true and perform payment on list
protected boolean canPayListAtOnce() { return false; }
protected CardCollectionView doListPayment(SpellAbility ability, CardCollectionView targetCards) { return CardCollection.EMPTY; }
protected CardCollectionView doListPayment(SpellAbility ability, CardCollectionView targetCards, final boolean effect) { return CardCollection.EMPTY; }
/**
* TODO: Write javadoc for this method.
@@ -157,8 +157,8 @@ public abstract class CostPartWithList extends CostPart {
public abstract String getHashForCardList();
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
executePayment(ai, ability, decision.cards);
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability, final boolean effect) {
executePayment(ai, ability, decision.cards, effect);
reportPaidCardsTo(ability);
return true;
}

View File

@@ -19,7 +19,6 @@ package forge.game.cost;
import com.google.common.base.Strings;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CounterEnumType;
import forge.game.player.Player;
@@ -47,7 +46,8 @@ public class CostPayEnergy extends CostPart {
@Override
public int paymentOrder() { return 7; }
public Integer getMaxAmountX(final SpellAbility ability, final Player payer) {
@Override
public Integer getMaxAmountX(final SpellAbility ability, final Player payer, final boolean effect) {
return payer.getCounters(CounterEnumType.ENERGY);
}
@@ -83,17 +83,12 @@ public class CostPayEnergy extends CostPart {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
Integer amount = this.convertAmount();
if (amount == null) { // try to calculate when it's defined.
amount = AbilityUtils.calculateAmount(ability.getHostCard(), getAmount(), ability);
}
return payer.getCounters(CounterEnumType.ENERGY) >= amount;
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
return payer.getCounters(CounterEnumType.ENERGY) >= this.getAbilityAmount(ability);
}
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability, final boolean effect) {
paidAmount = decision.c;
return ai.payEnergy(paidAmount, null);
}

View File

@@ -17,7 +17,6 @@
*/
package forge.game.cost;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -62,36 +61,16 @@ public class CostPayLife extends CostPart {
}
@Override
public Integer getMaxAmountX(SpellAbility ability, Player payer) {
if (!payer.canPayLife(1)) {
public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) {
if (!payer.canPayLife(1, effect)) {
return 0;
}
return payer.getLife();
}
/*
* (non-Javadoc)
*
* @see
* forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
Integer amount = this.convertAmount();
if (amount == null) { // try to calculate when it's defined.
amount = AbilityUtils.calculateAmount(ability.getHostCard(), getAmount(), ability);
// CR 107.1b
if (getAmount().contains("/Half")) {
amount = Math.max(amount, 0);
}
}
if (amount != null && !payer.canPayLife(amount)) {
return false;
}
if (!ability.isTrigger() && payer.hasKeyword("You can't pay life to cast spells or activate abilities.")) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
if (!payer.canPayLife(this.getAbilityAmount(ability), effect)) {
return false;
}
@@ -99,8 +78,8 @@ public class CostPayLife extends CostPart {
}
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
return ai.payLife(decision.c, null);
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability, final boolean effect) {
return ai.payLife(decision.c, null, effect);
}
public <T> T accept(ICostVisitor<T> visitor) {

View File

@@ -92,7 +92,7 @@ public class CostPayment extends ManaConversionMatrix {
}
cost = CostAdjustment.adjust(cost, ability);
return cost.canPay(ability);
return cost.canPay(ability, false);
}
/**
@@ -146,7 +146,7 @@ public class CostPayment extends ManaConversionMatrix {
((CostPartMana)part).setCardMatrix(this);
}
if (pd == null || !part.payAsDecided(decisionMaker.getPlayer(), pd, ability)) {
if (pd == null || !part.payAsDecided(decisionMaker.getPlayer(), pd, ability, decisionMaker.isEffect())) {
if (part instanceof CostPartMana) {
((CostPartMana)part).setCardMatrix(null);
}
@@ -194,7 +194,7 @@ public class CostPayment extends ManaConversionMatrix {
// wrap the payment and push onto the cost stack
game.costPaymentStack.push(part, this);
if ((decisionMaker.paysRightAfterDecision() || payImmediately) && !part.payAsDecided(decisionMaker.getPlayer(), decision, ability)) {
if ((decisionMaker.paysRightAfterDecision() || payImmediately) && !part.payAsDecided(decisionMaker.getPlayer(), decision, ability, decisionMaker.isEffect())) {
game.costPaymentStack.pop(); // cost is resolved
return false;
}
@@ -207,7 +207,7 @@ public class CostPayment extends ManaConversionMatrix {
// wrap the payment and push onto the cost stack
game.costPaymentStack.push(part, this);
if (!part.payAsDecided(decisionMaker.getPlayer(), decisions.get(part), this.ability)) {
if (!part.payAsDecided(decisionMaker.getPlayer(), decisions.get(part), this.ability, decisionMaker.isEffect())) {
game.costPaymentStack.pop(); // cost is resolved
return false;
}

View File

@@ -18,7 +18,6 @@
package forge.game.cost;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
@@ -119,15 +118,11 @@ public class CostPutCardToLib extends CostPartWithList {
}
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
final Game game = source.getGame();
Integer i = convertAmount();
if (i == null) {
i = AbilityUtils.calculateAmount(source, getAmount(), ability);
}
int i = getAbilityAmount(ability);
CardCollectionView typeList;
if (sameZone) {
@@ -162,7 +157,7 @@ public class CostPutCardToLib extends CostPartWithList {
}
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
return targetCard.getGame().getAction().moveToLibrary(targetCard, Integer.parseInt(getLibPos()),null);
}

View File

@@ -138,7 +138,7 @@ public class CostPutCounter extends CostPartWithList {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
if (this.payCostFromSource()) {
return source.canReceiveCounters(this.counter);
@@ -160,23 +160,20 @@ public class CostPutCounter extends CostPartWithList {
* forge.Card, forge.card.cost.Cost_Payment)
*/
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability, final boolean effect) {
if (this.payCostFromSource()) {
executePayment(ability, ability.getHostCard());
executePayment(ability, ability.getHostCard(), effect);
} else {
executePayment(ai, ability, decision.cards);
executePayment(ai, ability, decision.cards, effect);
}
triggerCounterPutAll(ability);
return true;
}
/* (non-Javadoc)
* @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
final Integer i = this.convertAmount();
targetCard.addCounter(this.getCounter(), i, ability.getActivatingPlayer(), null, ability.getRootAbility().isTrigger(), counterTable);
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
final int i = this.getAbilityAmount(ability);
targetCard.addCounter(this.getCounter(), i, ability.getActivatingPlayer(), null, effect, counterTable);
return targetCard;
}

View File

@@ -61,7 +61,7 @@ public class CostRemoveAnyCounter extends CostPart {
public int paymentOrder() { return 8; }
@Override
public Integer getMaxAmountX(final SpellAbility ability, final Player payer) {
public Integer getMaxAmountX(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
CardCollectionView validCards = CardLists.getValidCards(payer.getCardsIn(ZoneType.Battlefield), this.getType().split(";"), payer, source, ability);
@@ -86,8 +86,8 @@ public class CostRemoveAnyCounter extends CostPart {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
return AbilityUtils.calculateAmount(ability.getHostCard(), this.getAmount(), ability) <= getMaxAmountX(ability, payer);
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
return AbilityUtils.calculateAmount(ability.getHostCard(), this.getAmount(), ability) <= getMaxAmountX(ability, payer, effect);
}
/*
@@ -111,7 +111,7 @@ public class CostRemoveAnyCounter extends CostPart {
}
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability, final boolean effect) {
int removed = 0;
for (Entry<GameEntity, Map<CounterType, Integer>> e : decision.counterTable.row(Optional.absent()).entrySet()) {
for (Entry<CounterType, Integer> v : e.getValue().entrySet()) {

View File

@@ -72,7 +72,7 @@ public class CostRemoveCounter extends CostPart {
public int paymentOrder() { return 8; }
@Override
public Integer getMaxAmountX(final SpellAbility ability, final Player payer) {
public Integer getMaxAmountX(final SpellAbility ability, final Player payer, final boolean effect) {
final CounterType cntrs = this.counter;
final Card source = ability.getHostCard();
final String type = this.getType();
@@ -107,12 +107,12 @@ public class CostRemoveCounter extends CostPart {
sb.append("-").append(this.getAmount());
} else {
sb.append("Remove ");
final Integer i = this.convertAmount();
if (this.getAmount().equals("X")) {
sb.append("any number of counters");
} else if (this.getAmount().equals("All")) {
sb.append("all ").append(this.counter.getName().toLowerCase()).append(" counters");
} else {
final Integer i = this.convertAmount();
sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(),
this.counter.getName().toLowerCase() + " counter"));
}
@@ -137,20 +137,20 @@ public class CostRemoveCounter extends CostPart {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final CounterType cntrs = this.counter;
final Card source = ability.getHostCard();
final String type = this.getType();
final Integer amount;
final int amount;
if (getAmount().equals("All")) {
amount = source.getCounters(cntrs);
}
else {
amount = this.convertAmount();
amount = getAbilityAmount(ability);
}
if (this.payCostFromSource()) {
return (amount == null) || ((source.getCounters(cntrs) - amount) >= 0);
return (source.getCounters(cntrs) - amount) >= 0;
}
else {
List<Card> typeList;
@@ -159,22 +159,20 @@ public class CostRemoveCounter extends CostPart {
} else {
typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability);
}
if (amount != null) {
// (default logic) remove X counters from a single permanent
for (Card c : typeList) {
if (c.getCounters(cntrs) - amount >= 0) {
return true;
}
}
return false;
}
}
return true;
return false;
}
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability, final boolean effect) {
int removed = 0;
final int toRemove = decision.c;

View File

@@ -53,7 +53,7 @@ public class CostReturn extends CostPartWithList {
public int paymentOrder() { return 10; }
@Override
public Integer getMaxAmountX(SpellAbility ability, Player payer) {
public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) {
final Card source = ability.getHostCard();
CardCollectionView typeList = payer.getCardsIn(ZoneType.Battlefield);
@@ -102,21 +102,20 @@ public class CostReturn extends CostPartWithList {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
if (payCostFromSource()) {
return source.isInPlay();
}
final Integer amount = this.convertAmount();
return amount == null || getMaxAmountX(ability, payer) >= amount;
return getMaxAmountX(ability, payer, effect) >= getAbilityAmount(ability);
}
/* (non-Javadoc)
* @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
return targetCard.getGame().getAction().moveToHand(targetCard, null);
}

View File

@@ -64,7 +64,7 @@ public class CostReveal extends CostPartWithList {
}
@Override
public Integer getMaxAmountX(SpellAbility ability, Player payer) {
public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) {
final Card source = ability.getHostCard();
CardCollectionView handList = payer.getCardsIn(revealFrom);
if (ability.isSpell()) {
@@ -78,20 +78,17 @@ public class CostReveal extends CostPartWithList {
}
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
CardCollectionView handList = payer.getCardsIn(revealFrom);
final Integer amount = this.convertAmount();
final int amount = this.getAbilityAmount(ability);
if (this.payCostFromSource()) {
return revealFrom.contains(source.getLastKnownZone().getZoneType());
} else if (this.getType().equals("Hand")) {
return true;
} else if (this.getType().equals("SameColor")) {
if (amount == null) {
return false;
}
for (final Card card : handList) {
if (CardLists.filter(handList, new Predicate<Card>() {
@Override
@@ -104,7 +101,7 @@ public class CostReveal extends CostPartWithList {
}
return false;
} else {
return (amount == null) || (amount <= getMaxAmountX(ability, payer));
return amount <= getMaxAmountX(ability, payer, effect);
}
}
@@ -151,7 +148,7 @@ public class CostReveal extends CostPartWithList {
}
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
targetCard.getGame().getAction().reveal(new CardCollection(targetCard), ability.getActivatingPlayer());
StringBuilder sb = new StringBuilder();
sb.append(ability.getActivatingPlayer());
@@ -172,7 +169,7 @@ public class CostReveal extends CostPartWithList {
}
@Override
protected CardCollectionView doListPayment(SpellAbility ability, CardCollectionView targetCards) {
protected CardCollectionView doListPayment(SpellAbility ability, CardCollectionView targetCards, final boolean effect) {
ability.getActivatingPlayer().getGame().getAction().reveal(targetCards, ability.getActivatingPlayer());
return targetCards;
}

View File

@@ -23,9 +23,6 @@ import forge.game.spellability.SpellAbility;
public class CostRevealChosenPlayer extends CostPart {
/**
* Serializables need a version ID.
*/
private static final long serialVersionUID = 1L;
public CostRevealChosenPlayer() { }
@@ -40,22 +37,15 @@ public class CostRevealChosenPlayer extends CostPart {
return "Reveal the player you chose";
}
/*
* (non-Javadoc)
*
* @see
* forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player activator) {
public final boolean canPay(final SpellAbility ability, final Player activator, final boolean effect) {
final Card source = ability.getHostCard();
return source.getChosenPlayer() != null && source.getTurnInController().equals(activator);
}
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability, final boolean effect) {
ability.getHostCard().revealChosenPlayer();
return true;
}

View File

@@ -9,9 +9,6 @@ import forge.game.spellability.SpellAbility;
*/
public class CostRollDice extends CostPart {
/**
* Serializables need a version ID.
*/
private static final long serialVersionUID = 1L;
private final String resultSVar;
@@ -27,15 +24,8 @@ public class CostRollDice extends CostPart {
this.resultSVar = resultSVar;
}
/*
* (non-Javadoc)
*
* @see
* forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility,
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
return true;
}
@@ -55,7 +45,7 @@ public class CostRollDice extends CostPart {
}
@Override
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa) {
public boolean payAsDecided(Player payer, PaymentDecision pd, SpellAbility sa, final boolean effect) {
int sides = Integer.parseInt(getType());
int result = RollDiceEffect.rollDiceForPlayer(sa, payer, pd.c, sides);
sa.setSVar(resultSVar, Integer.toString(result));

View File

@@ -21,7 +21,6 @@ import org.apache.commons.lang3.ObjectUtils;
import com.google.common.collect.Iterables;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
@@ -58,11 +57,11 @@ public class CostSacrifice extends CostPartWithList {
public int paymentOrder() { return 15; }
@Override
public Integer getMaxAmountX(SpellAbility ability, Player payer) {
public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) {
final Card source = ability.getHostCard();
CardCollectionView typeList = payer.getCardsIn(ZoneType.Battlefield);
typeList = CardLists.getValidCards(typeList, getType().split(";"), payer, source, ability);
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability, effect));
return typeList.size();
}
@@ -101,38 +100,35 @@ public class CostSacrifice extends CostPartWithList {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player activator) {
public final boolean canPay(final SpellAbility ability, final Player activator, final boolean effect) {
final Card source = ability.getHostCard();
if (getType().equals("OriginalHost")) {
Card originalEquipment = ability.getOriginalHost();
return originalEquipment.isEquipping();
return originalEquipment.isEquipping() && originalEquipment.canBeSacrificedBy(ability, effect);
}
else if (!payCostFromSource()) { // You can always sac all
if ("All".equalsIgnoreCase(getAmount())) {
CardCollectionView typeList = activator.getCardsIn(ZoneType.Battlefield);
typeList = CardLists.getValidCards(typeList, getType().split(";"), activator, source, ability);
// it needs to check if everything can be sacrificed
return Iterables.all(typeList, CardPredicates.canBeSacrificedBy(ability));
return Iterables.all(typeList, CardPredicates.canBeSacrificedBy(ability, effect));
}
Integer amount = this.convertAmount();
if (amount == null) {
amount = AbilityUtils.calculateAmount(source, getAmount(), ability);
}
int amount = getAbilityAmount(ability);
return getMaxAmountX(ability, activator) >= amount;
return getMaxAmountX(ability, activator, effect) >= amount;
// If amount is null, it's either "ALL" or "X"
// if X is defined, it needs to be calculated and checked, if X is
// choice, it can be Paid even if it's 0
}
else return source.canBeSacrificedBy(ability);
else return source.canBeSacrificedBy(ability, effect);
}
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
// no table there, it is already handled by CostPartWithList
return targetCard.getGame().getAction().sacrifice(targetCard, ability, null, null);
return targetCard.getGame().getAction().sacrifice(targetCard, ability, effect, null, null);
}
/* (non-Javadoc)

View File

@@ -59,13 +59,13 @@ public class CostTap extends CostPart {
}
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
return source.isUntapped() && (!source.isSick() || source.hasKeyword("CARDNAME may activate abilities as though it has haste."));
}
@Override
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) {
public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability, final boolean effect) {
ability.getHostCard().tap(true);
return true;
}

View File

@@ -54,7 +54,7 @@ public class CostTapType extends CostPartWithList {
}
@Override
public Integer getMaxAmountX(SpellAbility ability, Player payer) {
public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) {
final Card source = ability.getHostCard();
// extend if cards use X with different conditions
@@ -126,7 +126,7 @@ public class CostTapType extends CostPartWithList {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
String type = this.getType();
@@ -169,15 +169,15 @@ public class CostTapType extends CostPartWithList {
return CardLists.getTotalPower(typeList, true, ability.hasParam("Crew")) >= i;
}
final Integer amount = this.convertAmount();
return (typeList.size() != 0) && ((amount == null) || (typeList.size() >= amount));
final int amount = this.getAbilityAmount(ability);
return (typeList.size() != 0) && (typeList.size() >= amount);
}
/* (non-Javadoc)
* @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
targetCard.tap(true);
return targetCard;
}

View File

@@ -69,7 +69,7 @@ public class CostUnattach extends CostPartWithList {
* forge.Card, forge.Player, forge.card.cost.Cost)
*/
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) {
final Card source = ability.getHostCard();
final String type = this.getType();
@@ -107,7 +107,7 @@ public class CostUnattach extends CostPartWithList {
* @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card)
*/
@Override
protected Card doPayment(SpellAbility ability, Card targetCard) {
protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) {
targetCard.unattachFromEntity(targetCard.getEntityAttachedTo());
return targetCard;
}

Some files were not shown because too many files have changed in this diff Show More