From aa6f2e3b6c99a661797cc261c407c729660c3ea2 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 12 Dec 2021 05:36:39 +0000 Subject: [PATCH] Cost: rework param that says if it's by effect or by Spell/ActivatedAbility cost --- .../java/forge/ai/AiAttackController.java | 6 +- .../src/main/java/forge/ai/AiController.java | 18 +- .../main/java/forge/ai/AiCostDecision.java | 149 ++++----------- .../src/main/java/forge/ai/ComputerUtil.java | 44 ++--- .../main/java/forge/ai/ComputerUtilCard.java | 30 +-- .../java/forge/ai/ComputerUtilCombat.java | 18 +- .../main/java/forge/ai/ComputerUtilCost.java | 20 +- .../main/java/forge/ai/ComputerUtilMana.java | 50 ++--- .../java/forge/ai/PlayerControllerAi.java | 26 +-- .../src/main/java/forge/ai/SpecialCardAi.java | 12 +- .../main/java/forge/ai/SpellAbilityAi.java | 8 +- .../main/java/forge/ai/ability/AnimateAi.java | 10 +- .../main/java/forge/ai/ability/AttachAi.java | 6 +- .../forge/ai/ability/ChangeTargetsAi.java | 2 +- .../java/forge/ai/ability/ChangeZoneAi.java | 10 +- .../java/forge/ai/ability/ChooseColorAi.java | 2 +- .../ai/ability/ChooseGenericEffectAi.java | 2 +- .../java/forge/ai/ability/ChooseTypeAi.java | 2 +- .../forge/ai/ability/CopyPermanentAi.java | 2 +- .../main/java/forge/ai/ability/CounterAi.java | 4 +- .../forge/ai/ability/CountersMultiplyAi.java | 2 +- .../java/forge/ai/ability/CountersPutAi.java | 4 +- .../forge/ai/ability/CountersPutAllAi.java | 2 +- .../forge/ai/ability/CountersRemoveAi.java | 4 +- .../java/forge/ai/ability/DamageAllAi.java | 6 +- .../java/forge/ai/ability/DamageDealAi.java | 14 +- .../forge/ai/ability/DelayedTriggerAi.java | 4 +- .../main/java/forge/ai/ability/DestroyAi.java | 8 +- .../java/forge/ai/ability/DestroyAllAi.java | 2 +- .../src/main/java/forge/ai/ability/DigAi.java | 4 +- .../java/forge/ai/ability/DigUntilAi.java | 2 +- .../main/java/forge/ai/ability/DiscardAi.java | 6 +- .../main/java/forge/ai/ability/DrawAi.java | 4 +- .../main/java/forge/ai/ability/EffectAi.java | 2 +- .../forge/ai/ability/ImmediateTriggerAi.java | 2 +- .../java/forge/ai/ability/LifeGainAi.java | 4 +- .../java/forge/ai/ability/LifeLoseAi.java | 9 +- .../main/java/forge/ai/ability/LifeSetAi.java | 4 +- .../java/forge/ai/ability/ManifestAi.java | 2 +- .../main/java/forge/ai/ability/MillAi.java | 2 +- .../java/forge/ai/ability/PermanentAi.java | 8 +- .../forge/ai/ability/PermanentCreatureAi.java | 2 +- .../main/java/forge/ai/ability/PlayAi.java | 2 +- .../main/java/forge/ai/ability/PumpAi.java | 18 +- .../main/java/forge/ai/ability/RepeatAi.java | 2 +- .../java/forge/ai/ability/SacrificeAi.java | 11 +- .../java/forge/ai/ability/SetStateAi.java | 2 +- .../java/forge/ai/ability/StoreSVarAi.java | 2 +- .../src/main/java/forge/ai/ability/TapAi.java | 2 +- .../main/java/forge/ai/ability/TokenAi.java | 8 +- .../java/forge/ai/ability/UnattachAllAi.java | 2 +- .../main/java/forge/ai/ability/UntapAi.java | 4 +- .../SpellAbilityChoicesIterator.java | 2 +- .../ai/simulation/SpellAbilityPicker.java | 10 +- .../src/main/java/forge/game/ForgeScript.java | 8 +- .../src/main/java/forge/game/GameAction.java | 9 +- .../game/ability/SpellAbilityEffect.java | 4 +- .../game/ability/effects/BalanceEffect.java | 4 +- .../ability/effects/ChooseGenericEffect.java | 16 +- .../game/ability/effects/DestroyEffect.java | 2 +- .../game/ability/effects/DiscardEffect.java | 24 +-- .../ability/effects/SacrificeAllEffect.java | 4 +- .../game/ability/effects/SacrificeEffect.java | 10 +- .../src/main/java/forge/game/card/Card.java | 29 +-- .../java/forge/game/card/CardPredicates.java | 4 +- .../java/forge/game/card/CardProperty.java | 3 +- .../src/main/java/forge/game/cost/Cost.java | 12 +- .../java/forge/game/cost/CostAddMana.java | 4 +- .../java/forge/game/cost/CostAdjustment.java | 4 +- .../game/cost/CostChooseCreatureType.java | 4 +- .../main/java/forge/game/cost/CostDamage.java | 4 +- .../game/cost/CostDecisionMakerBase.java | 7 +- .../java/forge/game/cost/CostDiscard.java | 29 +-- .../main/java/forge/game/cost/CostDraw.java | 15 +- .../main/java/forge/game/cost/CostExert.java | 11 +- .../main/java/forge/game/cost/CostExile.java | 17 +- .../forge/game/cost/CostExileFromStack.java | 11 +- .../game/cost/CostExiledMoveToGrave.java | 16 +- .../java/forge/game/cost/CostFlipCoin.java | 4 +- .../java/forge/game/cost/CostGainControl.java | 11 +- .../java/forge/game/cost/CostGainLife.java | 38 +--- .../main/java/forge/game/cost/CostMill.java | 18 +- .../main/java/forge/game/cost/CostPart.java | 10 +- .../java/forge/game/cost/CostPartMana.java | 6 +- .../forge/game/cost/CostPartWithList.java | 18 +- .../java/forge/game/cost/CostPayEnergy.java | 15 +- .../java/forge/game/cost/CostPayLife.java | 33 +--- .../java/forge/game/cost/CostPayment.java | 8 +- .../forge/game/cost/CostPutCardToLib.java | 11 +- .../java/forge/game/cost/CostPutCounter.java | 17 +- .../forge/game/cost/CostRemoveAnyCounter.java | 8 +- .../forge/game/cost/CostRemoveCounter.java | 28 ++- .../main/java/forge/game/cost/CostReturn.java | 9 +- .../main/java/forge/game/cost/CostReveal.java | 15 +- .../game/cost/CostRevealChosenPlayer.java | 14 +- .../java/forge/game/cost/CostRollDice.java | 14 +- .../java/forge/game/cost/CostSacrifice.java | 24 +-- .../main/java/forge/game/cost/CostTap.java | 4 +- .../java/forge/game/cost/CostTapType.java | 10 +- .../java/forge/game/cost/CostUnattach.java | 4 +- .../main/java/forge/game/cost/CostUntap.java | 11 +- .../java/forge/game/cost/CostUntapType.java | 12 +- .../java/forge/game/phase/PhaseHandler.java | 2 +- .../main/java/forge/game/player/Player.java | 33 ++-- .../forge/game/player/PlayerController.java | 10 +- .../forge/game/player/PlayerPredicates.java | 4 +- .../StaticAbilityCantDiscard.java | 43 +++++ .../StaticAbilityCantGainLosePayLife.java | 36 +++- .../StaticAbilityCantSacrifice.java | 42 ++++ .../main/java/forge/game/zone/MagicStack.java | 4 +- .../util/PlayerControllerForTests.java | 6 +- .../res/cardsfolder/a/angel_of_jubilation.txt | 4 +- forge-gui/res/cardsfolder/a/assault_suit.txt | 5 +- .../res/cardsfolder/t/tajuru_preserver.txt | 3 +- .../t/tamiyo_collector_of_tales.txt | 3 +- .../y/yasharn_implacable_earth.txt | 3 +- .../gamemodes/match/input/InputPayMana.java | 8 +- .../input/InputPayManaOfCostPayment.java | 9 +- .../java/forge/player/HumanCostDecision.java | 179 ++++-------------- .../src/main/java/forge/player/HumanPlay.java | 52 ++--- .../forge/player/HumanPlaySpellAbility.java | 4 +- .../forge/player/PlayerControllerHuman.java | 6 +- 122 files changed, 720 insertions(+), 912 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/staticability/StaticAbilityCantDiscard.java create mode 100644 forge-game/src/main/java/forge/game/staticability/StaticAbilityCantSacrifice.java diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index cbeeaaea18d..781a8916daf 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -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. diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index fc43bbf1bd0..746e217fe93 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -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) { diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 88b4bcef48a..ff2d79b90ae 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -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 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 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 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,25 +719,24 @@ 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) { - final String sVar = ability.getSVar(amount); - if (amount.equals("All")) { - c = source.getCounters(cost.counter); - } else if (sVar.equals("Targeted$CardManaCost")) { - c = 0; - if (ability.getTargets().size() > 0) { - for (Card tgt : ability.getTargets().getTargetCards()) { - if (tgt.getManaCost() != null) { - c += tgt.getManaCost().getCMC(); - } + int c; + + final String sVar = ability.getSVar(amount); + if (amount.equals("All")) { + c = source.getCounters(cost.counter); + } else if (sVar.equals("Targeted$CardManaCost")) { + c = 0; + if (ability.getTargets().size() > 0) { + for (Card tgt : ability.getTargets().getTargetCards()) { + if (tgt.getManaCost() != null) { + c += tgt.getManaCost().getCMC(); } } - } else { - c = AbilityUtils.calculateAmount(source, amount, ability); } + } else { + 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); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index e59039c30cf..f298876f5e4 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -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); } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index cea246c9771..e0c851d5987 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -618,32 +618,32 @@ public class ComputerUtilCard { } public static boolean canBeKilledByRoyalAssassin(final Player ai, final Card card) { - boolean wasTapped = card.isTapped(); - for (Player opp : ai.getOpponents()) { - for (Card c : opp.getCardsIn(ZoneType.Battlefield)) { - for (SpellAbility sa : c.getSpellAbilities()) { + boolean wasTapped = card.isTapped(); + for (Player opp : ai.getOpponents()) { + for (Card c : opp.getCardsIn(ZoneType.Battlefield)) { + for (SpellAbility sa : c.getSpellAbilities()) { if (sa.getApi() != ApiType.Destroy) { continue; } - if (!ComputerUtilCost.canPayCost(sa, opp)) { + if (!ComputerUtilCost.canPayCost(sa, opp, sa.isTrigger())) { continue; } sa.setActivatingPlayer(opp); if (sa.canTarget(card)) { - continue; + continue; } // check whether the ability can only target tapped creatures - card.setTapped(true); + card.setTapped(true); if (!sa.canTarget(card)) { - card.setTapped(wasTapped); - continue; + card.setTapped(wasTapped); + continue; } - card.setTapped(wasTapped); + card.setTapped(wasTapped); return true; - } - } - } - return false; + } + } + } + return false; } /** @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 717e0899a62..ce35cbd6650 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -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(); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index fdc52790d6f..7c5ab89b130 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -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 diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 292aa19adf1..9e09b366811 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -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 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(); } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index c94ce865d80..ce59b560300 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -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 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; diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 7584678db2e..2c58ee120b0 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -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 diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java index 400db29d152..7fb54cde205 100644 --- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java @@ -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.")); } diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java index 8fa5661bc1d..94c0f0e1bb3 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java index 363115939f3..4b2ea785e37 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -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; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeTargetsAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeTargetsAi.java index 3d9f69700b0..4dea256169d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeTargetsAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeTargetsAi.java @@ -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 diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 9530308fe7f..ddeb2473e2b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -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); } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseColorAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseColorAi.java index b395c6382ad..822440b4165 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseColorAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseColorAi.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java index 7f181a24776..6fb9e6c6e59 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java @@ -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; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseTypeAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseTypeAi.java index dedf551ada4..56cb2c95a21 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseTypeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseTypeAi.java @@ -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 diff --git a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java index 4cc13b47baa..c18d777bc9a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java @@ -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); } diff --git a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java index 7b5beebb67b..070a4025cdb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java @@ -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); } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java index 1b3f5d8c0e1..88daf52eaac 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index 7de981a9c22..ff4a0b0d8d4 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -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; diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java index 29e9565c142..0ae4a0187f3 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java @@ -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); diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java index 7ab8c24fbc0..2b68e99df54 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java @@ -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; diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java index 016c5a4878b..56e2bfcd06b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java @@ -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); diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java index 7e310dec65e..1127c5ec52a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -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 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)); } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java b/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java index 8513849621d..d963c4be1e9 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java @@ -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; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java index 7113db74e6f..699cdc4df17 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java @@ -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; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java index 1daa3a9991e..c21be6ff6f7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java @@ -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)); } diff --git a/forge-ai/src/main/java/forge/ai/ability/DigAi.java b/forge-ai/src/main/java/forge/ai/ability/DigAi.java index 9ae979ce786..367c435ca2a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigAi.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java index dc11d808114..93c4559952a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java index 723a55d10d6..e47a2e23804 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java @@ -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); } diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 9d7f339bf7e..6fc55a0a8b5 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -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 diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java index d3c3c4fe322..50abb9d9bc7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -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")) { diff --git a/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java b/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java index fe0830352a6..6c6ec70808f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java @@ -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(); diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java index e5bf58820e1..a47db66e293 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java @@ -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); } diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java index 41e62d105fb..95f9a1d3d0c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java @@ -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 { diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java index fbe6b154cfd..3568466bbf7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java @@ -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 { diff --git a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java index b5ddeb6f262..6062a19bb84 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java @@ -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; diff --git a/forge-ai/src/main/java/forge/ai/ability/MillAi.java b/forge-ai/src/main/java/forge/ai/ability/MillAi.java index ccaf7fcef14..ab6a00ce796 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MillAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MillAi.java @@ -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); } } diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java index 6bbf5b9b5fb..ce7fc140e1c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java index 924c8e86784..e18e73c0550 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java index b5fd974fca8..119315f7e47 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java @@ -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; diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index f645043bc99..73971e59feb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -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 { diff --git a/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java b/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java index 3a6cc6c5382..f34a321b6fd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java index 0b6f2e3ddc8..8ff5f887413 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java @@ -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 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); } } diff --git a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java index 8d38bd31b4e..b010942e831 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java @@ -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); } diff --git a/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java b/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java index 7e2cdf2423b..f2cd01e86d1 100644 --- a/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java @@ -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); } diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAi.java b/forge-ai/src/main/java/forge/ai/ability/TapAi.java index 3a088d45dba..6d23c619f4c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAi.java @@ -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(); diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java index 9bd2f20db2c..61d810267ea 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java @@ -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); } } diff --git a/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java b/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java index 5a14e5962a0..ca0f965975f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java @@ -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; diff --git a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java index 7c4dc3329af..881b922ab68 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java @@ -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() { @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java index 01431db679c..b2294c0cf39 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java @@ -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; } diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java index 11120549c47..36157977d19 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java @@ -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 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 { diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index 6183d6f2294..58e5c55a290 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -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; } } diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 4dbc116d2ef..ce0186dca77 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -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 params) { - if (!c.canBeSacrificedBy(source)) { + public final Card sacrifice(final Card c, final SpellAbility source, final boolean effect, CardZoneTable table, Map params) { + if (!c.canBeSacrificedBy(source, effect)) { return null; } diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index bd7cfb2ada7..ff0e821b3d7 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -682,13 +682,13 @@ public abstract class SpellAbilityEffect { }; } - protected static void discard(SpellAbility sa, CardZoneTable table, Map discardedMap) { + protected static void discard(SpellAbility sa, CardZoneTable table, final boolean effect, Map discardedMap) { Set 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")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java index c473fa857c7..0622cbd6f8a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java @@ -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); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java index 990139f880d..7fc21484e63 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java @@ -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,17 +53,10 @@ 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)) { - saToRemove.add(saChoice); - } - } else if (unlessCost.startsWith("Discard<")) { - if (!p.canDiscardBy(sa)) { - saToRemove.add(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); } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java index f7eaba7e38d..2ec6aa03fad 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DestroyEffect.java @@ -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); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java index cc7eb1f02ab..0b6a092e907 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java @@ -34,7 +34,7 @@ public class DiscardEffect extends SpellAbilityEffect { final String mode = sa.getParam("Mode"); final StringBuilder sb = new StringBuilder(); - final Iterable tgtPlayers = Iterables.filter(getTargetPlayers(sa), PlayerPredicates.canDiscardBy(sa)); + final Iterable 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); diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java index c8028fa4a4b..ec0a92f06b9 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeAllEffect.java @@ -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); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java index e09f48a01b6..4325f574b5d 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java @@ -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 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 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 runParams = AbilityKey.newMap(); - runParams.put(AbilityKey.Devoured, sac); + runParams.put(AbilityKey.Devoured, lKICopy); game.getTriggerHandler().runTrigger(TriggerType.Devoured, runParams, false); } if (exploit) { diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 9ee9bb5ea5c..179d090c15b 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -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, 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, 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, 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) { diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java index 736e629647b..4ca270b5072 100644 --- a/forge-game/src/main/java/forge/game/card/CardPredicates.java +++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java @@ -251,11 +251,11 @@ public final class CardPredicates { }; } - public static final Predicate canBeSacrificedBy(final SpellAbility sa) { + public static final Predicate canBeSacrificedBy(final SpellAbility sa, final boolean effect) { return new Predicate() { @Override public boolean apply(final Card c) { - return c.canBeSacrificedBy(sa); + return c.canBeSacrificedBy(sa, effect); } }; } diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index ba3ab337c9f..43a103b3460 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -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")) { diff --git a/forge-game/src/main/java/forge/game/cost/Cost.java b/forge-game/src/main/java/forge/game/cost/Cost.java index 84f2e52229c..47ba6109adf 100644 --- a/forge-game/src/main/java/forge/game/cost/Cost.java +++ b/forge-game/src/main/java/forge/game/cost/Cost.java @@ -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()) { diff --git a/forge-game/src/main/java/forge/game/cost/CostAddMana.java b/forge-game/src/main/java/forge/game/cost/CostAddMana.java index 080fb3db26d..8f644205782 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAddMana.java +++ b/forge-game/src/main/java/forge/game/cost/CostAddMana.java @@ -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 manaProduced = new ArrayList<>(); diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java index 013523bbe7e..90a597fd40f 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -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"); diff --git a/forge-game/src/main/java/forge/game/cost/CostChooseCreatureType.java b/forge-game/src/main/java/forge/game/cost/CostChooseCreatureType.java index 1692f4088b2..f40b99464b4 100644 --- a/forge-game/src/main/java/forge/game/cost/CostChooseCreatureType.java +++ b/forge-game/src/main/java/forge/game/cost/CostChooseCreatureType.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostDamage.java b/forge-game/src/main/java/forge/game/cost/CostDamage.java index fa2e229a9b0..55cb70d3a34 100644 --- a/forge-game/src/main/java/forge/game/cost/CostDamage.java +++ b/forge-game/src/main/java/forge/game/cost/CostDamage.java @@ -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(); diff --git a/forge-game/src/main/java/forge/game/cost/CostDecisionMakerBase.java b/forge-game/src/main/java/forge/game/cost/CostDecisionMakerBase.java index 500ccd93588..a5cf64503b1 100644 --- a/forge-game/src/main/java/forge/game/cost/CostDecisionMakerBase.java +++ b/forge-game/src/main/java/forge/game/cost/CostDecisionMakerBase.java @@ -5,9 +5,14 @@ import forge.game.player.Player; public abstract class CostDecisionMakerBase implements ICostVisitor { 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; + } } diff --git a/forge-game/src/main/java/forge/game/cost/CostDiscard.java b/forge-game/src/main/java/forge/game/cost/CostDiscard.java index dd9cd0c12aa..25024ab32b7 100644 --- a/forge-game/src/main/java/forge/game/cost/CostDiscard.java +++ b/forge-game/src/main/java/forge/game/cost/CostDiscard.java @@ -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 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) diff --git a/forge-game/src/main/java/forge/game/cost/CostDraw.java b/forge-game/src/main/java/forge/game/cost/CostDraw.java index 02d5daf6925..6885be83ebe 100644 --- a/forge-game/src/main/java/forge/game/cost/CostDraw.java +++ b/forge-game/src/main/java/forge/game/cost/CostDraw.java @@ -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); } diff --git a/forge-game/src/main/java/forge/game/cost/CostExert.java b/forge-game/src/main/java/forge/game/cost/CostExert.java index 448f57cbf09..4e4fae78cae 100644 --- a/forge-game/src/main/java/forge/game/cost/CostExert.java +++ b/forge-game/src/main/java/forge/game/cost/CostExert.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostExile.java b/forge-game/src/main/java/forge/game/cost/CostExile.java index 415a2567c6c..4b1afc52e5a 100644 --- a/forge-game/src/main/java/forge/game/cost/CostExile.java +++ b/forge-game/src/main/java/forge/game/cost/CostExile.java @@ -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 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()); diff --git a/forge-game/src/main/java/forge/game/cost/CostExileFromStack.java b/forge-game/src/main/java/forge/game/cost/CostExileFromStack.java index 28bafa64813..627b416ffe0 100644 --- a/forge-game/src/main/java/forge/game/cost/CostExileFromStack.java +++ b/forge-game/src/main/java/forge/game/cost/CostExileFromStack.java @@ -32,9 +32,6 @@ import forge.game.zone.ZoneType; public class CostExileFromStack extends CostPart { // ExileFromStack - /** - * 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); diff --git a/forge-game/src/main/java/forge/game/cost/CostExiledMoveToGrave.java b/forge-game/src/main/java/forge/game/cost/CostExiledMoveToGrave.java index c030bf0efa3..bcfa56e4c75 100644 --- a/forge-game/src/main/java/forge/game/cost/CostExiledMoveToGrave.java +++ b/forge-game/src/main/java/forge/game/cost/CostExiledMoveToGrave.java @@ -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); } diff --git a/forge-game/src/main/java/forge/game/cost/CostFlipCoin.java b/forge-game/src/main/java/forge/game/cost/CostFlipCoin.java index 234a19cc0d1..9e8e3c30212 100644 --- a/forge-game/src/main/java/forge/game/cost/CostFlipCoin.java +++ b/forge-game/src/main/java/forge/game/cost/CostFlipCoin.java @@ -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); diff --git a/forge-game/src/main/java/forge/game/cost/CostGainControl.java b/forge-game/src/main/java/forge/game/cost/CostGainControl.java index 94a410776d4..3f2f905a12c 100644 --- a/forge-game/src/main/java/forge/game/cost/CostGainControl.java +++ b/forge-game/src/main/java/forge/game/cost/CostGainControl.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostGainLife.java b/forge-game/src/main/java/forge/game/cost/CostGainLife.java index 3341d11e8e5..b90b44ab28f 100644 --- a/forge-game/src/main/java/forge/game/cost/CostGainLife.java +++ b/forge-game/src/main/java/forge/game/cost/CostGainLife.java @@ -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 getPotentialTargets(final Player payer, final Card source) { - List res = new ArrayList<>(); + public List getPotentialTargets(final Player payer, final SpellAbility ability) { + List 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 possibleTargets = getPotentialTargets(payer, ability.getHostCard()); + List 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) { diff --git a/forge-game/src/main/java/forge/game/cost/CostMill.java b/forge-game/src/main/java/forge/game/cost/CostMill.java index 296bbc3e1f8..eb184a618d1 100644 --- a/forge-game/src/main/java/forge/game/cost/CostMill.java +++ b/forge-game/src/main/java/forge/game/cost/CostMill.java @@ -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); diff --git a/forge-game/src/main/java/forge/game/cost/CostPart.java b/forge-game/src/main/java/forge/game/cost/CostPart.java index 94c11232f59..34d5028497c 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPart.java +++ b/forge-game/src/main/java/forge/game/cost/CostPart.java @@ -74,7 +74,7 @@ public abstract class CostPart implements Comparable, 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, 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, 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 accept(final ICostVisitor visitor); @@ -191,7 +195,7 @@ public abstract class CostPart implements Comparable, 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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostPartMana.java b/forge-game/src/main/java/forge/game/cost/CostPartMana.java index c8d198efbf2..63bdcc50314 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPartMana.java +++ b/forge-game/src/main/java/forge/game/cost/CostPartMana.java @@ -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); } } diff --git a/forge-game/src/main/java/forge/game/cost/CostPartWithList.java b/forge-game/src/main/java/forge/game/cost/CostPartWithList.java index 8478ec38838..079ca1ecc7a 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPartWithList.java +++ b/forge-game/src/main/java/forge/game/cost/CostPartWithList.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostPayEnergy.java b/forge-game/src/main/java/forge/game/cost/CostPayEnergy.java index d5c128d2e5b..7a7a3696c66 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPayEnergy.java +++ b/forge-game/src/main/java/forge/game/cost/CostPayEnergy.java @@ -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); } diff --git a/forge-game/src/main/java/forge/game/cost/CostPayLife.java b/forge-game/src/main/java/forge/game/cost/CostPayLife.java index 7241e7ad61c..76078ffd415 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPayLife.java +++ b/forge-game/src/main/java/forge/game/cost/CostPayLife.java @@ -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 accept(ICostVisitor visitor) { diff --git a/forge-game/src/main/java/forge/game/cost/CostPayment.java b/forge-game/src/main/java/forge/game/cost/CostPayment.java index d822f07380a..31816f88bac 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPayment.java +++ b/forge-game/src/main/java/forge/game/cost/CostPayment.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java b/forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java index 8e4fe64c98a..9b7b765647c 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java +++ b/forge-game/src/main/java/forge/game/cost/CostPutCardToLib.java @@ -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); } diff --git a/forge-game/src/main/java/forge/game/cost/CostPutCounter.java b/forge-game/src/main/java/forge/game/cost/CostPutCounter.java index 4ef24a38cd4..f0858d81218 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPutCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostPutCounter.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java index 517b583083c..a9c6f2c0b60 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java @@ -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> e : decision.counterTable.row(Optional.absent()).entrySet()) { for (Entry v : e.getValue().entrySet()) { diff --git a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java b/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java index 6d99ca7792b..5906759cbad 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java @@ -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 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; - } + + // (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; diff --git a/forge-game/src/main/java/forge/game/cost/CostReturn.java b/forge-game/src/main/java/forge/game/cost/CostReturn.java index 7cceba28bc5..a10f0657adc 100644 --- a/forge-game/src/main/java/forge/game/cost/CostReturn.java +++ b/forge-game/src/main/java/forge/game/cost/CostReturn.java @@ -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); } diff --git a/forge-game/src/main/java/forge/game/cost/CostReveal.java b/forge-game/src/main/java/forge/game/cost/CostReveal.java index 909e07573f7..990cbb799e2 100644 --- a/forge-game/src/main/java/forge/game/cost/CostReveal.java +++ b/forge-game/src/main/java/forge/game/cost/CostReveal.java @@ -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() { @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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostRevealChosenPlayer.java b/forge-game/src/main/java/forge/game/cost/CostRevealChosenPlayer.java index 3e50343e0b1..425e372f2d6 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRevealChosenPlayer.java +++ b/forge-game/src/main/java/forge/game/cost/CostRevealChosenPlayer.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostRollDice.java b/forge-game/src/main/java/forge/game/cost/CostRollDice.java index b89278d23b9..32124056316 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRollDice.java +++ b/forge-game/src/main/java/forge/game/cost/CostRollDice.java @@ -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)); diff --git a/forge-game/src/main/java/forge/game/cost/CostSacrifice.java b/forge-game/src/main/java/forge/game/cost/CostSacrifice.java index bc7c73aac94..e36ae9fc0a3 100644 --- a/forge-game/src/main/java/forge/game/cost/CostSacrifice.java +++ b/forge-game/src/main/java/forge/game/cost/CostSacrifice.java @@ -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) diff --git a/forge-game/src/main/java/forge/game/cost/CostTap.java b/forge-game/src/main/java/forge/game/cost/CostTap.java index 8bd6893885e..2f57d20589d 100644 --- a/forge-game/src/main/java/forge/game/cost/CostTap.java +++ b/forge-game/src/main/java/forge/game/cost/CostTap.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostTapType.java b/forge-game/src/main/java/forge/game/cost/CostTapType.java index 3d5a8769874..ea8eda92a58 100644 --- a/forge-game/src/main/java/forge/game/cost/CostTapType.java +++ b/forge-game/src/main/java/forge/game/cost/CostTapType.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostUnattach.java b/forge-game/src/main/java/forge/game/cost/CostUnattach.java index a7964283360..8c0626f1d46 100644 --- a/forge-game/src/main/java/forge/game/cost/CostUnattach.java +++ b/forge-game/src/main/java/forge/game/cost/CostUnattach.java @@ -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; } diff --git a/forge-game/src/main/java/forge/game/cost/CostUntap.java b/forge-game/src/main/java/forge/game/cost/CostUntap.java index 60c5b7a9693..cbaff72257a 100644 --- a/forge-game/src/main/java/forge/game/cost/CostUntap.java +++ b/forge-game/src/main/java/forge/game/cost/CostUntap.java @@ -69,21 +69,14 @@ public class CostUntap extends CostPart { source.setTapped(true); } - /* - * (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) { final Card source = ability.getHostCard(); return source.isTapped() && (!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().untap(true); return true; } diff --git a/forge-game/src/main/java/forge/game/cost/CostUntapType.java b/forge-game/src/main/java/forge/game/cost/CostUntapType.java index 71b1ef3711e..5a65415c057 100644 --- a/forge-game/src/main/java/forge/game/cost/CostUntapType.java +++ b/forge-game/src/main/java/forge/game/cost/CostUntapType.java @@ -29,9 +29,7 @@ import forge.game.zone.ZoneType; * The Class CostUntapType. */ public class CostUntapType extends CostPartWithList { - /** - * Serializables need a version ID. - */ + private static final long serialVersionUID = 1L; public final boolean canUntapSource; @@ -77,7 +75,7 @@ public class CostUntapType 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 Player activator = ability.getActivatingPlayer(); final Card source = ability.getHostCard(); @@ -88,12 +86,12 @@ public class CostUntapType extends CostPartWithList { } typeList = CardLists.filter(typeList, Presets.TAPPED); - final Integer amount = convertAmount(); - return (typeList.size() != 0) && ((amount == null) || (typeList.size() >= amount)); + final int amount = this.getAbilityAmount(ability); + return (typeList.size() != 0) && (typeList.size() >= amount); } @Override - protected Card doPayment(SpellAbility ability, Card targetCard) { + protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) { targetCard.untap(true); return targetCard; } diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index 20c795e9fa8..efcec49d60c 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -399,7 +399,7 @@ public class PhaseHandler implements java.io.Serializable { final CardCollection discarded = new CardCollection(); boolean firstDiscarded = playerTurn.getNumDiscardedThisTurn() == 0; for (Card c : playerTurn.getController().chooseCardsToDiscardToMaximumHandSize(numDiscard)) { - if (playerTurn.discard(c, null, table) != null) { + if (playerTurn.discard(c, null, false, table) != null) { discarded.add(c); } } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 88273a4b5d0..00555eff5c8 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -104,6 +104,7 @@ import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbilityCantBeCast; +import forge.game.staticability.StaticAbilityCantDiscard; import forge.game.staticability.StaticAbilityCantDraw; import forge.game.staticability.StaticAbilityCantGainLosePayLife; import forge.game.staticability.StaticAbilityCantPutCounter; @@ -608,18 +609,18 @@ public class Player extends GameEntity implements Comparable { } public final boolean canLoseLife() { - return !hasLost() && !StaticAbilityCantGainLosePayLife.anyCantLosePayLife(this); + return !hasLost() && !StaticAbilityCantGainLosePayLife.anyCantLoseLife(this); } - public final boolean canPayLife(final int lifePayment) { + public final boolean canPayLife(final int lifePayment, final boolean effect) { if (lifePayment > 0 && life < lifePayment) { return false; } - return (lifePayment <= 0) || !StaticAbilityCantGainLosePayLife.anyCantLosePayLife(this); + return (lifePayment <= 0) || !StaticAbilityCantGainLosePayLife.anyCantPayLife(this, effect); } - public final boolean payLife(final int lifePayment, final Card source) { - if (!canPayLife(lifePayment)) { + public final boolean payLife(final int lifePayment, final Card source, final boolean effect) { + if (!canPayLife(lifePayment, effect)) { return false; } @@ -1519,11 +1520,11 @@ public class Player extends GameEntity implements Comparable { return numDrawnThisDrawStep; } - public final Card discard(final Card c, final SpellAbility sa, CardZoneTable table) { - return discard(c, sa , table, null); + public final Card discard(final Card c, final SpellAbility sa, final boolean effect, CardZoneTable table) { + return discard(c, sa, effect, table, null); } - public final Card discard(final Card c, final SpellAbility sa, CardZoneTable table, Map params) { - if (!c.canBeDiscardedBy(sa)) { + public final Card discard(final Card c, final SpellAbility sa, final boolean effect, CardZoneTable table, Map params) { + if (!c.canBeDiscardedBy(sa, effect)) { return null; } @@ -3335,20 +3336,12 @@ public class Player extends GameEntity implements Comparable { return CardLists.count(getAttachedCards(), CardPredicates.Presets.CURSE) > 0; } - public boolean canDiscardBy(SpellAbility sa) { + public boolean canDiscardBy(SpellAbility sa, final boolean effect) { if (sa == null) { return true; } - return !isOpponentOf(sa.getActivatingPlayer()) || !hasKeyword("Spells and abilities your opponents control can't cause you to discard cards."); - } - - public boolean canSacrificeBy(SpellAbility sa) { - if (sa == null) { - return true; - } - - return !isOpponentOf(sa.getActivatingPlayer()) || !hasKeyword("Spells and abilities your opponents control can't cause you to sacrifice permanents."); + return !StaticAbilityCantDiscard.cantDiscard(this, sa, effect); } public boolean canSearchLibraryWith(SpellAbility sa, Player targetPlayer) { @@ -3546,7 +3539,7 @@ public class Player extends GameEntity implements Comparable { table.put(ZoneType.Sideboard, ZoneType.Hand, moved); } else if (c.isInZone(ZoneType.Hand)) { // Discard and Draw boolean firstDiscard = getNumDiscardedThisTurn() == 0; - if (discard(c, sa, table) != null) { + if (discard(c, sa, true, table) != null) { // Change this if something would make multiple player learn at the same time // Discard Trigger outside Effect diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index 73307e15989..293541a0492 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -245,14 +245,14 @@ public abstract class PlayerController { public abstract void resetAtEndOfTurn(); // currently used by the AI to perform card memory cleanup - public final boolean payManaCost(CostPartMana costPartMana, SpellAbility sa, String prompt, boolean isActivatedAbility) { - return payManaCost(costPartMana, sa, prompt, null, isActivatedAbility); + public final boolean payManaCost(CostPartMana costPartMana, SpellAbility sa, String prompt, boolean effect) { + return payManaCost(costPartMana, sa, prompt, null, effect); } - public final boolean payManaCost(CostPartMana costPartMana, SpellAbility sa, String prompt, ManaConversionMatrix matrix, boolean isActivatedAbility) { - return payManaCost(costPartMana.getManaCostFor(sa), costPartMana, sa, prompt, matrix, isActivatedAbility); + public final boolean payManaCost(CostPartMana costPartMana, SpellAbility sa, String prompt, ManaConversionMatrix matrix, boolean effect) { + return payManaCost(costPartMana.getManaCostFor(sa), costPartMana, sa, prompt, matrix, effect); } - public abstract boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt, ManaConversionMatrix matrix, boolean isActivatedAbility); + public abstract boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt, ManaConversionMatrix matrix, boolean effect); public abstract Map chooseCardsForConvokeOrImprovise(SpellAbility sa, ManaCost manaCost, CardCollectionView untappedCards, boolean improvise); diff --git a/forge-game/src/main/java/forge/game/player/PlayerPredicates.java b/forge-game/src/main/java/forge/game/player/PlayerPredicates.java index c2744bda9b0..021a6ce959f 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerPredicates.java +++ b/forge-game/src/main/java/forge/game/player/PlayerPredicates.java @@ -23,11 +23,11 @@ public final class PlayerPredicates { }; } - public static final Predicate canDiscardBy(final SpellAbility source) { + public static final Predicate canDiscardBy(final SpellAbility source, final boolean effect) { return new Predicate() { @Override public boolean apply(final Player p) { - return p.canDiscardBy(source); + return p.canDiscardBy(source, effect); } }; } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantDiscard.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantDiscard.java new file mode 100644 index 00000000000..f3b7749e4a4 --- /dev/null +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantDiscard.java @@ -0,0 +1,43 @@ +package forge.game.staticability; + +import forge.game.Game; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +public class StaticAbilityCantDiscard { + + static String MODE = "CantDiscard"; + + public static boolean cantDiscard(final Player player, final SpellAbility cause, final boolean effect) { + final Game game = player.getGame(); + for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) { + continue; + } + + if (applyCantDiscardAbility(stAb, player, cause, effect)) { + return true; + } + } + } + return false; + } + + public static boolean applyCantDiscardAbility(final StaticAbility stAb, final Player player, final SpellAbility cause, final boolean effect) { + if (!stAb.matchesValidParam("ValidPlayer", player)) { + return false; + } + if (stAb.hasParam("ForCost")) { + if ("True".equalsIgnoreCase(stAb.getParam("ForCost")) == effect) { + return false; + } + } + if (!stAb.matchesValidParam("ValidCause", cause)) { + return false; + } + return true; + } +} diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantGainLosePayLife.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantGainLosePayLife.java index 59cfaeecf8d..baa71410222 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantGainLosePayLife.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantGainLosePayLife.java @@ -9,6 +9,7 @@ public class StaticAbilityCantGainLosePayLife { static String MODE_CANT_GAIN_LIFE = "CantGainLife"; static String MODE_CANT_CHANGE_LIFE = "CantChangeLife"; + static String MODE_CANT_PAY_LIFE = "CantPayLife"; public static boolean anyCantGainLife(final Player player) { final Game game = player.getGame(); @@ -29,14 +30,45 @@ public class StaticAbilityCantGainLosePayLife { return false; } - public static boolean anyCantLosePayLife(final Player player) { + public static boolean anyCantLoseLife(final Player player) { final Game game = player.getGame(); for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { for (final StaticAbility stAb : ca.getStaticAbilities()) { - if (!stAb.getParam("Mode").equals(MODE_CANT_CHANGE_LIFE) || stAb.isSuppressed() || !stAb.checkConditions()) { + if (stAb.isSuppressed() || !stAb.checkConditions()) { continue; } + if (!stAb.getParam("Mode").equals(MODE_CANT_CHANGE_LIFE)) { + continue; + } + + if (applyCommonAbility(stAb, player)) { + return true; + } + } + } + + return false; + } + + public static boolean anyCantPayLife(final Player player, final boolean effect) { + final Game game = player.getGame(); + for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (stAb.isSuppressed() || !stAb.checkConditions()) { + continue; + } + + if (!(stAb.getParam("Mode").equals(MODE_CANT_PAY_LIFE) || stAb.getParam("Mode").equals(MODE_CANT_CHANGE_LIFE))) { + continue; + } + + if (stAb.hasParam("ForCost")) { + if ("True".equalsIgnoreCase(stAb.getParam("ForCost")) == effect) { + continue; + } + } + if (applyCommonAbility(stAb, player)) { return true; } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantSacrifice.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantSacrifice.java new file mode 100644 index 00000000000..86598d04d68 --- /dev/null +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantSacrifice.java @@ -0,0 +1,42 @@ +package forge.game.staticability; + +import forge.game.Game; +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +public class StaticAbilityCantSacrifice { + + static String MODE = "CantSacrifice"; + + public static boolean cantSacrifice(final Card card, final SpellAbility cause, final boolean effect) { + final Game game = card.getGame(); + for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) { + continue; + } + + if (applyCantSacrificeAbility(stAb, card, cause, effect)) { + return true; + } + } + } + return false; + } + + public static boolean applyCantSacrificeAbility(final StaticAbility stAb, final Card card, final SpellAbility cause, final boolean effect) { + if (!stAb.matchesValidParam("ValidCard", card)) { + return false; + } + if (stAb.hasParam("ForCost")) { + if ("True".equalsIgnoreCase(stAb.getParam("ForCost")) == effect) { + return false; + } + } + if (!stAb.matchesValidParam("ValidCause", cause)) { + return false; + } + return true; + } +} diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 05ac328f28a..50b148ccf1c 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -318,7 +318,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable | Planeswalker$ True | Defined$ You | ValidCards$ Card.nonLand | SubAbility$ DBDig | StackDescription$ SpellDescription | SpellDescription$ Choose a nonland card name, then reveal the top four cards of your library. Put all cards with the chosen name from among them into your hand and the rest into your graveyard. SVar:DBDig:DB$ Dig | DigNum$ 4 | Reveal$ True | ChangeNum$ All | ChangeValid$ Card.NamedCard | DestinationZone2$ Graveyard A:AB$ ChangeZone | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | TgtPrompt$ Choose target card in your graveyard | ValidTgts$ Card.YouCtrl | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Return target card from your graveyard to your hand. diff --git a/forge-gui/res/cardsfolder/y/yasharn_implacable_earth.txt b/forge-gui/res/cardsfolder/y/yasharn_implacable_earth.txt index 15f8ca69485..65d1a670371 100755 --- a/forge-gui/res/cardsfolder/y/yasharn_implacable_earth.txt +++ b/forge-gui/res/cardsfolder/y/yasharn_implacable_earth.txt @@ -5,6 +5,7 @@ PT:4/4 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Self | Execute$ TrigForest | TriggerDescription$ When NICKNAME enters the battlefield, search your library for a basic Forest card and a basic Plains card, reveal those cards, put them into your hand, then shuffle. SVar:TrigForest:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Forest.Basic | ChangeNum$ 1 | SubAbility$ DBPlains SVar:DBPlains:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Plains.Basic | ChangeNum$ 1 -S:Mode$ Continuous | Affected$ Player | AddKeyword$ You can't pay life to cast spells or activate abilities. & You can't sacrifice nonland permanents to cast spells or activate abilities. | Description$ Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities. +S:Mode$ CantPayLife | ValidPlayer$ Player | ForCost$ True | Description$ Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities. +S:Mode$ CantSacrifice | ValidCard$ Permanent.nonLand | ForCost$ True | Secondary$ True | Description$ Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities. AI:RemoveDeck:Random Oracle:When Yasharn enters the battlefield, search your library for a basic Forest card and a basic Plains card, reveal those cards, put them into your hand, then shuffle.\nPlayers can't pay life or sacrifice nonland permanents to cast spells or activate abilities. diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java index 0159ec383ae..87d9ae1866c 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java @@ -43,6 +43,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { protected final Game game; protected ManaCostBeingPaid manaCost; protected final SpellAbility saPaidFor; + protected boolean effect; private final boolean wasFloatingMana; private final Queue delaySelectCards = new LinkedList<>(); @@ -51,11 +52,12 @@ public abstract class InputPayMana extends InputSyncronizedBase { private boolean locked = false; - protected InputPayMana(final PlayerControllerHuman controller, final SpellAbility saPaidFor0, final Player player0) { + protected InputPayMana(final PlayerControllerHuman controller, final SpellAbility saPaidFor0, final Player player0, final boolean effect) { super(controller); player = player0; game = player.getGame(); saPaidFor = saPaidFor0; + this.effect = effect; //if player is floating mana, show mana pool to make it easier to use that mana wasFloatingMana = !player.getManaPool().isEmpty(); @@ -388,7 +390,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { final Runnable proc = new Runnable() { @Override public void run() { - ComputerUtilMana.payManaCost(manaCost, saPaidFor, player); + ComputerUtilMana.payManaCost(manaCost, saPaidFor, player, effect); } }; //must run in game thread as certain payment actions can only be automated there @@ -421,7 +423,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { Evaluator proc = new Evaluator() { @Override public Boolean evaluate() { - return ComputerUtilMana.canPayManaCost(manaCost, saPaidFor, player); + return ComputerUtilMana.canPayManaCost(manaCost, saPaidFor, player, effect); } }; runAsAi(proc); diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayManaOfCostPayment.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayManaOfCostPayment.java index 7b84fe7df0b..a817ba8a959 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayManaOfCostPayment.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayManaOfCostPayment.java @@ -14,8 +14,9 @@ import forge.util.ITriggerEvent; import forge.util.Localizer; public class InputPayManaOfCostPayment extends InputPayMana { - public InputPayManaOfCostPayment(final PlayerControllerHuman controller, ManaCostBeingPaid cost, SpellAbility spellAbility, Player payer, ManaConversionMatrix matrix) { - super(controller, spellAbility, payer); + + public InputPayManaOfCostPayment(final PlayerControllerHuman controller, ManaCostBeingPaid cost, SpellAbility spellAbility, Player payer, ManaConversionMatrix matrix, boolean effect) { + super(controller, spellAbility, payer, effect); manaCost = cost; extraMatrix = matrix; applyMatrix(); @@ -32,7 +33,7 @@ public class InputPayManaOfCostPayment extends InputPayMana { @Override protected final void onPlayerSelected(Player selected, final ITriggerEvent triggerEvent) { if (player == selected) { - if (player.canPayLife(this.phyLifeToLose + 2)) { + if (player.canPayLife(this.phyLifeToLose + 2, this.effect)) { if (manaCost.payPhyrexian()) { this.phyLifeToLose += 2; } else { @@ -51,7 +52,7 @@ public class InputPayManaOfCostPayment extends InputPayMana { protected void done() { final Card source = saPaidFor.getHostCard(); if (this.phyLifeToLose > 0) { - player.payLife(this.phyLifeToLose, source); + player.payLife(this.phyLifeToLose, source, this.effect); } } diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 96ae4850e45..8c13cda6fa0 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -17,7 +17,6 @@ import forge.game.GameEntity; import forge.game.GameEntityCounterTable; import forge.game.GameEntityView; import forge.game.GameEntityViewMap; -import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; @@ -50,11 +49,11 @@ public class HumanCostDecision extends CostDecisionMakerBase { private final Card source; private String orString = null; - public HumanCostDecision(final PlayerControllerHuman controller, final Player p, final SpellAbility sa, final Card source) { - this(controller, p, sa, source, null); + public HumanCostDecision(final PlayerControllerHuman controller, final Player p, final SpellAbility sa, final boolean effect, final Card source) { + this(controller, p, sa, effect, source, null); } - public HumanCostDecision(final PlayerControllerHuman controller, final Player p, final SpellAbility sa, final Card source, final String orString) { - super(p); + public HumanCostDecision(final PlayerControllerHuman controller, final Player p, final SpellAbility sa, final boolean effect, final Card source, final String orString) { + super(p, effect); this.controller = controller; ability = sa; this.source = source; @@ -63,11 +62,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostAddMana cost) { - Integer c = cost.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); - } - return PaymentDecision.number(c); + return PaymentDecision.number(cost.getAbilityAmount(ability)); } @Override @@ -83,7 +78,6 @@ public class HumanCostDecision extends CostDecisionMakerBase { public PaymentDecision visit(final CostDiscard cost) { CardCollectionView hand = player.getCardsIn(ZoneType.Hand); final String discardType = cost.getType(); - final String amount = cost.getAmount(); if (cost.payCostFromSource()) { return hand.contains(source) ? PaymentDecision.card(source) : null; @@ -104,13 +98,9 @@ public class HumanCostDecision extends CostDecisionMakerBase { return hand.contains(lastDrawn) ? PaymentDecision.card(lastDrawn) : null; } - Integer c = cost.convertAmount(); + int c = cost.getAbilityAmount(ability); if (discardType.equals("Random")) { - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } - CardCollectionView randomSubset = Aggregates.random(hand, c, new CardCollection()); if (randomSubset.size() > 1 && ability.getActivatingPlayer() != null) { randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability); @@ -175,10 +165,6 @@ public class HumanCostDecision extends CostDecisionMakerBase { final String[] validType = type.split(";"); hand = CardLists.getValidCards(hand, validType, player, source, ability); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } - final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, c, c, hand, ability); inp.setMessage(Localizer.getInstance().getMessage("lblSelectNMoreTargetTypeCardToDiscard", "%d", cost.getDescriptiveType())); inp.setCancelAllowed(true); @@ -191,12 +177,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostDamage cost) { - final String amount = cost.getAmount(); - - Integer c = cost.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + int c = cost.getAbilityAmount(ability); if (controller.confirmPayment(cost, Localizer.getInstance().getMessage("lblDoYouWantCardDealNDamageToYou", CardTranslation.getTranslatedName(source.getName()), String.valueOf(c)), ability)) { return PaymentDecision.number(c); @@ -206,14 +187,11 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final 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 res = cost.getPotentialPlayers(player, ability); @@ -239,10 +217,8 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostExile cost) { - final String amount = cost.getAmount(); final Game game = player.getGame(); - Integer c = cost.convertAmount(); String type = cost.getType(); boolean fromTopGrave = false; if (type.contains("FromTopGrave")) { @@ -272,9 +248,8 @@ public class HumanCostDecision extends CostDecisionMakerBase { return PaymentDecision.card(list); } list = CardLists.getValidCards(list, type.split(";"), player, source, ability); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + + int c = cost.getAbilityAmount(ability); if (cost.from == ZoneType.Battlefield || cost.from == ZoneType.Hand) { final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, c, c, list, ability); @@ -337,10 +312,8 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostExileFromStack cost) { - final String amount = cost.getAmount(); final Game game = player.getGame(); - Integer c = cost.convertAmount(); final String type = cost.getType(); final List saList = new ArrayList<>(); final List descList = new ArrayList<>(); @@ -361,9 +334,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { if (type.equals("All")) { return PaymentDecision.spellabilities(saList); } - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + int c = cost.getAbilityAmount(ability); if (saList.size() < c) { return null; @@ -423,10 +394,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostExiledMoveToGrave cost) { - Integer c = cost.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); - } + int c = cost.getAbilityAmount(ability); final Player activator = ability.getActivatingPlayer(); final CardCollection list = CardLists.getValidCards(activator.getGame().getCardsIn(ZoneType.Exile), @@ -454,7 +422,6 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostExert cost) { - final String amount = cost.getAmount(); final String type = cost.getType(); CardCollectionView list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source, ability); @@ -466,11 +433,8 @@ public class HumanCostDecision extends CostDecisionMakerBase { return null; } - Integer c = cost.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } - if (0 == c.intValue()) { + int c = cost.getAbilityAmount(ability); + if (0 == c) { return PaymentDecision.number(0); } if (list.size() < c) { @@ -490,12 +454,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostFlipCoin cost) { - final String amount = cost.getAmount(); - Integer c = cost.convertAmount(); - - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + Integer c = cost.getAbilityAmount(ability); if (!controller.confirmPayment(cost, Localizer.getInstance().getMessage("lblDoYouWantFlipNCoinAction", String.valueOf(c)), ability)) { return null; @@ -506,12 +465,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostRollDice cost) { - final String amount = cost.getAmount(); - Integer c = cost.convertAmount(); - - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + int c = cost.getAbilityAmount(ability); if (!controller.confirmPayment(cost, Localizer.getInstance().getMessage("lblDoYouWantRollNDiceAction", String.valueOf(c), "d" + cost.getType()), ability)) { return null; @@ -522,12 +476,8 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostGainControl cost) { - final String amount = cost.getAmount(); + int c = cost.getAbilityAmount(ability); - Integer c = cost.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } final CardCollectionView list = player.getCardsIn(ZoneType.Battlefield); final CardCollectionView validCards = CardLists.getValidCards(list, cost.getType().split(";"), player, source, ability); @@ -543,15 +493,10 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostGainLife cost) { - final String amount = cost.getAmount(); - - Integer c = cost.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + int c = cost.getAbilityAmount(ability); final List oppsThatCanGainLife = new ArrayList<>(); - for (final Player opp : cost.getPotentialTargets(player, source)) { + for (final Player opp : cost.getPotentialTargets(player, ability)) { if (opp.canGainLife()) { oppsThatCanGainLife.add(opp); } @@ -571,16 +516,11 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostMill cost) { - final String amount = cost.getAmount(); - Integer c = cost.convertAmount(); - - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + Integer c = cost.getAbilityAmount(ability); String message = null; if (orString != null && !orString.isEmpty()) { - message = Localizer.getInstance().getMessage("lblDoYouWantMillNCardsOrDoAction", String.valueOf(amount), orString); + message = Localizer.getInstance().getMessage("lblDoYouWantMillNCardsOrDoAction", String.valueOf(c), orString); } else { message = Localizer.getInstance().getMessage("lblMillNCardsFromYourLibraryConfirm", String.valueOf(c)); } @@ -593,12 +533,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostPayLife cost) { - final String amount = cost.getAmount(); - - Integer c = cost.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + Integer c = cost.getAbilityAmount(ability); if (ability.getPayCosts().isMandatory()) { return PaymentDecision.number(c); @@ -606,13 +541,13 @@ public class HumanCostDecision extends CostDecisionMakerBase { String message = null; if (orString != null && !orString.isEmpty()) { - message = Localizer.getInstance().getMessage("lblDoYouWantPayNLife", String.valueOf(amount), orString); + message = Localizer.getInstance().getMessage("lblDoYouWantPayNLife", String.valueOf(c), orString); } else { message = Localizer.getInstance().getMessage("lblPayNLifeConfirm", String.valueOf(c)); } // for costs declared mandatory, this is only reachable with a valid amount - if (player.canPayLife(c) && controller.confirmPayment(cost, message, ability)) { + if (player.canPayLife(c, isEffect()) && controller.confirmPayment(cost, message, ability)) { return PaymentDecision.number(c); } return null; @@ -620,12 +555,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostPayEnergy cost) { - final String amount = cost.getAmount(); - - Integer c = cost.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + Integer c = cost.getAbilityAmount(ability); if (player.canPayEnergy(c) && controller.confirmPayment(cost, Localizer.getInstance().getMessage("lblPayEnergyConfirm", cost.toString(), String.valueOf(player.getCounters(CounterEnumType.ENERGY)), "{E}"), ability)) { @@ -642,12 +572,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostPutCardToLib cost) { - final String amount = cost.getAmount(); - Integer c = cost.convertAmount(); - - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + Integer c = cost.getAbilityAmount(ability); final CardCollection list = CardLists.getValidCards(cost.sameZone ? player.getGame().getCardsIn(cost.getFrom()) : player.getCardsIn(cost.getFrom()), cost.getType().split(";"), player, source, ability); @@ -729,7 +654,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostPutCounter cost) { - final Integer c = cost.convertAmount(); + final int c = cost.getAbilityAmount(ability); if (cost.payCostFromSource()) { cost.setLastPaidAmount(c); @@ -753,12 +678,8 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostReturn cost) { - final String amount = cost.getAmount(); - Integer c = cost.convertAmount(); + int c = cost.getAbilityAmount(ability); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } if (cost.payCostFromSource()) { final Card card = ability.getHostCard(); if (card.getController() == player && card.isInPlay()) { @@ -783,8 +704,6 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostReveal cost) { - final String amount = cost.getAmount(); - if (cost.payCostFromSource()) { return PaymentDecision.card(source); } @@ -793,7 +712,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { } InputSelectCardsFromList inp = null; if (cost.getType().equals("SameColor")) { - final Integer num = cost.convertAmount(); + final Integer num = cost.getAbilityAmount(ability); CardCollectionView hand = player.getCardsIn(cost.getRevealFrom()); final CardCollectionView hand2 = hand; hand = CardLists.filter(hand, new Predicate() { @@ -824,14 +743,11 @@ public class HumanCostDecision extends CostDecisionMakerBase { }; inp.setMessage(Localizer.getInstance().getMessage("lblSelectNCardOfSameColorToReveal", String.valueOf(num))); } else { - Integer num = cost.convertAmount(); + Integer num = cost.getAbilityAmount(ability); CardCollectionView hand = player.getCardsIn(cost.getRevealFrom()); hand = CardLists.getValidCards(hand, cost.getType().split(";"), player, source, ability); - if (num == null) { - num = AbilityUtils.calculateAmount(source, amount, ability); - } if (hand.size() < num) { return null; } @@ -860,12 +776,9 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostRemoveAnyCounter cost) { - Integer c = cost.convertAmount(); + int c = cost.getAbilityAmount(ability); final String type = cost.getType(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); - } CardCollectionView list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source, ability); list = CardLists.filter(list, CardPredicates.hasCounters()); @@ -989,14 +902,11 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostRemoveCounter cost) { final String amount = cost.getAmount(); - final Integer c = cost.convertAmount(); final String type = cost.getType(); int cntRemoved = 1; - if (c != null) { - cntRemoved = c.intValue(); - } else if (!amount.equals("All")) { - cntRemoved = AbilityUtils.calculateAmount(source, amount, ability); + if (!amount.equals("All")) { + cntRemoved = cost.getAbilityAmount(ability); } if (cost.payCostFromSource()) { @@ -1062,7 +972,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { final String amount = cost.getAmount(); final String type = cost.getType(); - CardCollectionView list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.canBeSacrificedBy(ability)); + CardCollectionView list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.canBeSacrificedBy(ability, isEffect())); list = CardLists.getValidCards(list, type.split(";"), player, source, ability); if (cost.payCostFromSource()) { @@ -1084,11 +994,8 @@ public class HumanCostDecision extends CostDecisionMakerBase { return PaymentDecision.card(list); } - Integer c = cost.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } - if (0 == c.intValue()) { + int c = cost.getAbilityAmount(ability); + if (0 == c) { return PaymentDecision.number(0); } if (list.size() < c) { @@ -1117,7 +1024,6 @@ public class HumanCostDecision extends CostDecisionMakerBase { public PaymentDecision visit(final CostTapType cost) { String type = cost.getType(); final String amount = cost.getAmount(); - Integer c = cost.convertAmount(); boolean sameType = false; if (type.contains(".sharesCreatureTypeWith")) { @@ -1141,8 +1047,9 @@ public class HumanCostDecision extends CostDecisionMakerBase { typeList = CardLists.getNotKeyword(typeList, "CARDNAME can't crew Vehicles."); } - if (c == null && !amount.equals("Any")) { - c = AbilityUtils.calculateAmount(source, amount, ability); + Integer c = null; + if (!amount.equals("Any")) { + c = cost.getAbilityAmount(ability); } if (c != null && c == 0) { @@ -1222,11 +1129,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { if (!cost.canUntapSource) { typeList.remove(source); } - final String amount = cost.getAmount(); - Integer c = cost.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } + int c = cost.getAbilityAmount(ability); final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, c, c, typeList, ability); inp.setCancelAllowed(true); inp.setMessage(Localizer.getInstance().getMessage("lblSelectATargetToUntap", cost.getDescriptiveType(), "%d")); diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 395974fbac6..87a976a6ade 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -267,13 +267,13 @@ public class HumanPlay { } } - final HumanCostDecision hcd = new HumanCostDecision(controller, p, sourceAbility, source, orString); + final HumanCostDecision hcd = new HumanCostDecision(controller, p, sourceAbility, true, source, orString); boolean mandatory = cost.isMandatory(); //the following costs do not need inputs for (CostPart part : parts) { // early bail to check if the part can be paid - if (!part.canPay(sourceAbility, p)) { + if (!part.canPay(sourceAbility, p, hcd.isEffect())) { return false; } @@ -296,7 +296,7 @@ public class HumanPlay { if (pd == null) { return false; } - part.payAsDecided(p, pd, sourceAbility); + part.payAsDecided(p, pd, sourceAbility, hcd.isEffect()); } else if (part instanceof CostAddMana) { String desc = part.toString(); @@ -310,7 +310,7 @@ public class HumanPlay { if (pd == null) { return false; } - part.payAsDecided(p, pd, sourceAbility); + part.payAsDecided(p, pd, sourceAbility, hcd.isEffect()); } else if (part instanceof CostExile) { CostExile costExile = (CostExile) part; @@ -321,7 +321,7 @@ public class HumanPlay { return false; } - costExile.payAsDecided(p, PaymentDecision.card(p.getCardsIn(ZoneType.Graveyard)), sourceAbility); + costExile.payAsDecided(p, PaymentDecision.card(p.getCardsIn(ZoneType.Graveyard)), sourceAbility, hcd.isEffect()); } else { from = costExile.getFrom(); CardCollection list = CardLists.getValidCards(p.getCardsIn(from), part.getType().split(";"), p, source, sourceAbility); @@ -334,7 +334,7 @@ public class HumanPlay { return false; } list = list.subList(0, nNeeded); - costExile.payAsDecided(p, PaymentDecision.card(list), sourceAbility); + costExile.payAsDecided(p, PaymentDecision.card(list), sourceAbility, hcd.isEffect()); } else { // replace this with input CardCollection newList = new CardCollection(); @@ -351,7 +351,7 @@ public class HumanPlay { } newList.add(gameCacheList.remove(cv)); } - costExile.payAsDecided(p, PaymentDecision.card(newList), sourceAbility); + costExile.payAsDecided(p, PaymentDecision.card(newList), sourceAbility, hcd.isEffect()); } } } @@ -401,7 +401,7 @@ public class HumanPlay { } } - costExile.payAsDecided(p, PaymentDecision.spellabilities(payList), sourceAbility); + costExile.payAsDecided(p, PaymentDecision.spellabilities(payList), sourceAbility, hcd.isEffect()); } else if (part instanceof CostPutCardToLib) { int amount = Integer.parseInt(part.getAmount()); @@ -451,7 +451,7 @@ public class HumanPlay { } } else { // Tainted Specter, Gurzigost, etc. - boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblPutIntoLibrary") + orString); + boolean hasPaid = payCostPart(controller, p, sourceAbility, hcd.isEffect(), (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblPutIntoLibrary") + orString); if (!hasPaid) { return false; } @@ -461,13 +461,13 @@ public class HumanPlay { else if (part instanceof CostGainControl) { int amount = Integer.parseInt(part.getAmount()); CardCollectionView list = CardLists.getValidCards(p.getGame().getCardsIn(ZoneType.Battlefield), part.getType(), p, source, sourceAbility); - boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblGainControl") + orString); + boolean hasPaid = payCostPart(controller, p, sourceAbility, hcd.isEffect(), (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblGainControl") + orString); if (!hasPaid) { return false; } } else if (part instanceof CostReturn) { CardCollectionView list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source, sourceAbility); int amount = getAmountFromPartX(part, source, sourceAbility); - boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblReturnToHand") + orString); + boolean hasPaid = payCostPart(controller, p, sourceAbility, hcd.isEffect(), (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblReturnToHand") + orString); if (!hasPaid) { return false; } } else if (part instanceof CostDiscard) { @@ -477,16 +477,16 @@ public class HumanPlay { return false; } - ((CostDiscard)part).payAsDecided(p, PaymentDecision.card(p.getCardsIn(ZoneType.Hand)), sourceAbility); + ((CostDiscard)part).payAsDecided(p, PaymentDecision.card(p.getCardsIn(ZoneType.Hand)), sourceAbility, true); } else if ("Random".equals(part.getType())) { if (!p.getController().confirmPayment(part, Localizer.getInstance().getMessage("lblWouldYouLikeRandomDiscardTargetCard", amount), sourceAbility)) { return false; } - ((CostDiscard)part).payAsDecided(p, PaymentDecision.card(Aggregates.random(p.getCardsIn(ZoneType.Hand), amount, new CardCollection())), sourceAbility); + ((CostDiscard)part).payAsDecided(p, PaymentDecision.card(Aggregates.random(p.getCardsIn(ZoneType.Hand), amount, new CardCollection())), sourceAbility, true); } else { CardCollectionView list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType(), p, source, sourceAbility); - boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lbldiscard") + orString); + boolean hasPaid = payCostPart(controller, p, sourceAbility, hcd.isEffect(), (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lbldiscard") + orString); if (!hasPaid) { return false; } } } @@ -494,14 +494,14 @@ public class HumanPlay { CostReveal costReveal = (CostReveal) part; CardCollectionView list = CardLists.getValidCards(p.getCardsIn(costReveal.getRevealFrom()), part.getType(), p, source, sourceAbility); int amount = getAmountFromPartX(part, source, sourceAbility); - boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblReveal") + orString); + boolean hasPaid = payCostPart(controller, p, sourceAbility, hcd.isEffect(), (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblReveal") + orString); if (!hasPaid) { return false; } } else if (part instanceof CostTapType) { CardCollectionView list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType(), p, source, sourceAbility); list = CardLists.filter(list, Presets.UNTAPPED); int amount = getAmountFromPartX(part, source, sourceAbility); - boolean hasPaid = payCostPart(controller, p, sourceAbility, (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblTap") + orString); + boolean hasPaid = payCostPart(controller, p, sourceAbility, hcd.isEffect(), (CostPartWithList)part, amount, list, Localizer.getInstance().getMessage("lblTap") + orString); if (!hasPaid) { return false; } } else if (part instanceof CostPartMana) { @@ -547,14 +547,14 @@ public class HumanPlay { } sourceAbility.clearManaPaid(); - boolean paid = p.getController().payManaCost(cost.getCostMana(), sourceAbility, prompt, false); + boolean paid = p.getController().payManaCost(cost.getCostMana(), sourceAbility, prompt, hcd.isEffect()); if (!paid) { p.getManaPool().refundManaPaid(sourceAbility); } return paid; } - private static boolean payCostPart(final PlayerControllerHuman controller, Player p, SpellAbility sourceAbility, CostPartWithList cpl, int amount, CardCollectionView list, String actionName) { + private static boolean payCostPart(final PlayerControllerHuman controller, Player p, SpellAbility sourceAbility, boolean effect, CostPartWithList cpl, int amount, CardCollectionView list, String actionName) { if (list.size() < amount) { return false; } // unable to pay (not enough cards) InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, amount, amount, list, sourceAbility); @@ -567,7 +567,7 @@ public class HumanPlay { return false; } - cpl.payAsDecided(p, PaymentDecision.card(inp.getSelected()), sourceAbility); + cpl.payAsDecided(p, PaymentDecision.card(inp.getSelected()), sourceAbility, effect); return true; } @@ -591,7 +591,7 @@ public class HumanPlay { final Card offering = ability.getSacrificedAsOffering(); offering.setUsedToPay(false); if (!manaInputCancelled) { - game.getAction().sacrifice(offering, ability, table, null); + game.getAction().sacrifice(offering, ability, false, table, null); } ability.resetSacrificedAsOffering(); } @@ -599,7 +599,7 @@ public class HumanPlay { final Card emerge = ability.getSacrificedAsEmerge(); emerge.setUsedToPay(false); if (!manaInputCancelled) { - game.getAction().sacrifice(emerge, ability, table, null); + game.getAction().sacrifice(emerge, ability, false, table, null); } ability.resetSacrificedAsEmerge(); } @@ -618,7 +618,7 @@ public class HumanPlay { return !manaInputCancelled; } - public static boolean payManaCost(final PlayerControllerHuman controller, final ManaCost realCost, final CostPartMana mc, final SpellAbility ability, final Player activator, String prompt, ManaConversionMatrix matrix, boolean isActivatedSa) { + public static boolean payManaCost(final PlayerControllerHuman controller, final ManaCost realCost, final CostPartMana mc, final SpellAbility ability, final Player activator, String prompt, ManaConversionMatrix matrix, boolean effect) { final Card source = ability.getHostCard(); ManaCostBeingPaid toPay = new ManaCostBeingPaid(realCost, mc.getRestriction()); @@ -653,7 +653,7 @@ public class HumanPlay { } CardCollection cardsToDelve = new CardCollection(); - if (isActivatedSa) { + if (!effect) { CostAdjustment.adjust(toPay, ability, cardsToDelve, false); } @@ -677,7 +677,7 @@ public class HumanPlay { } if (!toPay.isPaid()) { // Input is somehow clearing out the offering card? - inpPayment = new InputPayManaOfCostPayment(controller, toPay, ability, activator, matrix); + inpPayment = new InputPayManaOfCostPayment(controller, toPay, ability, activator, matrix, effect); inpPayment.setMessagePrefix(prompt); inpPayment.showAndWait(); if (!inpPayment.isPaid()) { @@ -695,7 +695,7 @@ public class HumanPlay { if (ability.getSacrificedAsOffering() != null) { System.out.println("Finishing up Offering"); offering.setUsedToPay(false); - activator.getGame().getAction().sacrifice(offering, ability, null, null); + activator.getGame().getAction().sacrifice(offering, ability, false, null, null); ability.resetSacrificedAsOffering(); } } @@ -706,7 +706,7 @@ public class HumanPlay { if (ability.getSacrificedAsEmerge() != null) { System.out.println("Finishing up Emerge"); emerge.setUsedToPay(false); - activator.getGame().getAction().sacrifice(emerge, ability, null, null); + activator.getGame().getAction().sacrifice(emerge, ability, false, null, null); ability.resetSacrificedAsEmerge(); } } diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 251bf43ca65..f3c5bf69906 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -127,7 +127,7 @@ public class HumanPlaySpellAbility { payment.setSnowForColor(true); } - if (ability.isAbility() && ability.isActivatedAbility()) { + if (ability.isActivatedAbility()) { final Map params = Maps.newHashMap(); for (KeywordInterface inst : c.getKeywords()) { @@ -155,7 +155,7 @@ public class HumanPlaySpellAbility { && (!mayChooseTargets || ability.setupTargets()) // if you can choose targets, then do choose them. && ability.canCastTiming(human) && ability.checkRestrictions(human) - && (isFree || payment.payCost(new HumanCostDecision(controller, human, ability, ability.getHostCard()))); + && (isFree || payment.payCost(new HumanCostDecision(controller, human, ability, false, ability.getHostCard()))); if (!prerequisitesMet) { if (!ability.isTrigger()) { diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 31548a29587..64ad2a362a8 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -443,7 +443,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont max = Math.min(max, AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("XMaxLimit"), ability)); } if (cost != null) { - Integer costX = cost.getMaxForNonManaX(ability, player); + Integer costX = cost.getMaxForNonManaX(ability, player, false); if (costX != null) { max = Math.min(max, costX); } @@ -2083,8 +2083,8 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont @Override public boolean payManaCost(final ManaCost toPay, final CostPartMana costPartMana, final SpellAbility sa, - final String prompt, ManaConversionMatrix matrix, final boolean isActivatedSa) { - return HumanPlay.payManaCost(this, toPay, costPartMana, sa, player, prompt, matrix, isActivatedSa); + final String prompt, ManaConversionMatrix matrix, final boolean effect) { + return HumanPlay.payManaCost(this, toPay, costPartMana, sa, player, prompt, matrix, effect); } @Override