From bbf788594fac79eb3f6f8c4456dfae4490bdd173 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Wed, 23 Dec 2020 08:05:26 +0000 Subject: [PATCH] CostRemoveCounter + CostRemoveAnyCounter: refactor remove X counters from something you control --- .../src/main/java/forge/ai/AiController.java | 6 +- .../main/java/forge/ai/AiCostDecision.java | 355 ++++++++++-------- .../src/main/java/forge/ai/ComputerUtil.java | 14 +- .../main/java/forge/ai/ComputerUtilCost.java | 46 ++- .../main/java/forge/ai/ComputerUtilMana.java | 17 +- .../src/main/java/forge/ai/SpecialCardAi.java | 2 +- .../main/java/forge/ai/SpellAbilityAi.java | 6 - .../main/java/forge/ai/ability/AttachAi.java | 4 +- .../java/forge/ai/ability/ChangeZoneAi.java | 18 +- .../java/forge/ai/ability/ChooseColorAi.java | 2 +- .../java/forge/ai/ability/ChooseSourceAi.java | 2 +- .../java/forge/ai/ability/ControlGainAi.java | 2 +- .../main/java/forge/ai/ability/CounterAi.java | 8 +- .../java/forge/ai/ability/CountersPutAi.java | 12 +- .../forge/ai/ability/CountersPutAllAi.java | 4 +- .../ai/ability/CountersPutOrRemoveAi.java | 2 +- .../forge/ai/ability/CountersRemoveAi.java | 10 +- .../java/forge/ai/ability/DamageAllAi.java | 6 +- .../java/forge/ai/ability/DamageDealAi.java | 39 +- .../forge/ai/ability/DamagePreventAi.java | 2 +- .../forge/ai/ability/DamagePreventAllAi.java | 2 +- .../main/java/forge/ai/ability/DebuffAi.java | 6 +- .../main/java/forge/ai/ability/DestroyAi.java | 2 +- .../java/forge/ai/ability/DestroyAllAi.java | 4 +- .../src/main/java/forge/ai/ability/DigAi.java | 6 +- .../java/forge/ai/ability/DigUntilAi.java | 6 +- .../main/java/forge/ai/ability/DiscardAi.java | 6 +- .../main/java/forge/ai/ability/DrawAi.java | 14 +- .../java/forge/ai/ability/LifeGainAi.java | 11 +- .../java/forge/ai/ability/LifeLoseAi.java | 19 +- .../main/java/forge/ai/ability/LifeSetAi.java | 10 +- .../java/forge/ai/ability/ManifestAi.java | 2 +- .../main/java/forge/ai/ability/MillAi.java | 10 +- .../main/java/forge/ai/ability/ProtectAi.java | 9 +- .../java/forge/ai/ability/ProtectAllAi.java | 2 +- .../main/java/forge/ai/ability/PumpAi.java | 84 ++--- .../main/java/forge/ai/ability/RepeatAi.java | 8 +- .../java/forge/ai/ability/SacrificeAi.java | 6 +- .../java/forge/ai/ability/SacrificeAllAi.java | 4 +- .../java/forge/ai/ability/StoreSVarAi.java | 2 +- .../src/main/java/forge/ai/ability/TapAi.java | 35 +- .../main/java/forge/ai/ability/TapAiBase.java | 59 ++- .../java/forge/ai/ability/TapOrUntapAi.java | 11 +- .../main/java/forge/ai/ability/TokenAi.java | 8 +- .../java/forge/ai/ability/UnattachAllAi.java | 2 +- .../main/java/forge/ai/ability/UntapAi.java | 24 +- .../src/main/java/forge/game/ForgeScript.java | 2 +- .../src/main/java/forge/game/GameAction.java | 6 - .../forge/game/GameEntityCounterTable.java | 34 +- .../src/main/java/forge/game/card/Card.java | 11 - .../java/forge/game/card/CardFactoryUtil.java | 45 --- .../java/forge/game/card/CardProperty.java | 2 +- .../java/forge/game/card/CounterType.java | 3 + .../src/main/java/forge/game/cost/Cost.java | 76 ++-- .../main/java/forge/game/cost/CostPart.java | 39 +- .../java/forge/game/cost/CostPayEnergy.java | 4 + .../forge/game/cost/CostRemoveAnyCounter.java | 116 ++---- .../forge/game/cost/CostRemoveCounter.java | 100 +++-- .../java/forge/game/cost/PaymentDecision.java | 55 +-- .../game/spellability/AbilityActivated.java | 4 +- .../game/spellability/AbilityManaPart.java | 2 +- .../game/spellability/AbilityStatic.java | 4 +- .../forge/game/spellability/SpellAbility.java | 25 +- .../spellability/SpellAbilityCondition.java | 3 +- .../game/spellability/TargetRestrictions.java | 27 +- .../main/java/forge/game/zone/MagicStack.java | 5 +- .../res/cardsfolder/b/blademane_baku.txt | 8 +- .../res/cardsfolder/c/chamber_sentry.txt | 2 +- .../cardsfolder/c/chisei_heart_of_oceans.txt | 4 +- .../cardsfolder/g/garruk_primal_hunter.txt | 1 - .../res/cardsfolder/h/huatli_warrior_poet.txt | 3 +- forge-gui/res/cardsfolder/n/novijen_sages.txt | 3 +- forge-gui/res/cardsfolder/o/ooze_flux.txt | 3 +- .../res/cardsfolder/p/petalmane_baku.txt | 8 +- forge-gui/res/cardsfolder/p/power_conduit.txt | 3 +- .../res/cardsfolder/q/quillmane_baku.txt | 8 +- .../r/retribution_of_the_ancients.txt | 3 +- .../res/cardsfolder/s/skullmane_baku.txt | 8 +- forge-gui/res/cardsfolder/s/soul_diviner.txt | 2 +- .../upcoming/tayam_luminous_enigma.txt | 12 + forge-gui/res/cardsfolder/w/waxmane_baku.txt | 7 +- .../java/forge/player/HumanCostDecision.java | 155 ++++---- .../src/main/java/forge/player/HumanPlay.java | 76 +--- .../forge/player/HumanPlaySpellAbility.java | 12 +- .../forge/player/PlayerControllerHuman.java | 23 +- .../java/forge/player/TargetSelection.java | 5 +- 86 files changed, 835 insertions(+), 983 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/tayam_luminous_enigma.txt diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index b54c6344cbb..7421fa0cffb 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -751,7 +751,7 @@ public class AiController { if (xPay <= 0) { return AiPlayDecision.CantAffordX; } - card.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } else if (mana.isZero()) { // if mana is zero, but card mana cost does have X, then something is wrong ManaCost cardCost = card.getManaCost(); @@ -1061,8 +1061,8 @@ public class AiController { } else if ("VolrathsShapeshifter".equals(sa.getParam("AILogic"))) { return SpecialCardAi.VolrathsShapeshifter.targetBestCreature(player, sa); } else if ("DiscardCMCX".equals(sa.getParam("AILogic"))) { - final int CMC = Integer.parseInt(sourceCard.getSVar("PayX")); - CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(CMC)); + final int cmc = Integer.parseInt(sa.getSVar("PayX")); + CardCollection discards = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc)); if (discards.isEmpty()) { return null; } else { diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index c2b8c9cf691..f5c43be501f 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -4,12 +4,15 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.ObjectUtils; + import com.google.common.base.Predicate; import com.google.common.base.Predicates; 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; @@ -20,6 +23,7 @@ import forge.game.card.CardPredicates.Presets; import forge.game.card.CounterEnumType; import forge.game.card.CounterType; import forge.game.cost.*; +import forge.game.keyword.Keyword; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; @@ -31,7 +35,7 @@ import forge.util.collect.FCollectionView; public class AiCostDecision extends CostDecisionMakerBase { private final SpellAbility ability; private final Card source; - + private final CardCollection discarded; private final CardCollection tapped; @@ -47,11 +51,11 @@ 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); } - + return PaymentDecision.number(c); } @@ -321,17 +325,17 @@ public class AiCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(CostGainLife cost) { final List oppsThatCanGainLife = Lists.newArrayList(); - + for (final Player opp : cost.getPotentialTargets(player, source)) { if (opp.canGainLife()) { oppsThatCanGainLife.add(opp); } } - + if (oppsThatCanGainLife.size() == 0) { return null; } - + return PaymentDecision.players(oppsThatCanGainLife); } @@ -587,7 +591,7 @@ public class AiCostDecision extends CostDecisionMakerBase { public PaymentDecision visit(CostReturn cost) { if (cost.payCostFromSource()) return PaymentDecision.card(source); - + Integer c = cost.convertAmount(); if (c == null) { c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); @@ -616,7 +620,7 @@ public class AiCostDecision extends CostDecisionMakerBase { if (cost.getType().equals("SameColor")) { return null; } - + hand = CardLists.getValidCards(hand, type.split(";"), player, source, ability); Integer c = cost.convertAmount(); if (c == null) { @@ -632,60 +636,162 @@ public class AiCostDecision extends CostDecisionMakerBase { return PaymentDecision.card(aic.getCardsToDiscard(c, type.split(";"), ability)); } + protected int removeCounter(GameEntityCounterTable table, List prefs, CounterEnumType cType, int stillToRemove) { + int removed = 0; + if (!prefs.isEmpty() && stillToRemove > 0) { + Collections.sort(prefs, CardPredicates.compareByCounterType(cType)); + + for (Card prefCard : prefs) { + // already enough removed + if (stillToRemove <= removed) { + break; + } + int thisRemove = Math.min(prefCard.getCounters(cType), stillToRemove); + if (thisRemove > 0) { + removed += thisRemove; + table.put(prefCard, CounterType.get(cType), thisRemove); + } + } + } + return removed; + } + @Override public PaymentDecision visit(CostRemoveAnyCounter cost) { final String amount = cost.getAmount(); final int c = AbilityUtils.calculateAmount(source, amount, ability); - final String type = cost.getType(); + final Card originalHost = ObjectUtils.defaultIfNull(ability.getOriginalHost(), source); - CardCollectionView typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source, ability); + if (c <= 0) { + return null; + } + + CardCollectionView typeList = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), cost.getType().split(";"), player, source, ability); + // only cards with counters are of interest + typeList = CardLists.filter(typeList, CardPredicates.hasCounters()); // no target if (typeList.isEmpty()) { return null; } + // TODO fill up a GameEntityCounterTable + // cost now has counter type or null + // the amount might be different from 1, could be X + // currently if amount is bigger than one, + // it tries to remove all counters from one source and type at once + + + int toRemove = 0; + final GameEntityCounterTable table = new GameEntityCounterTable(); + + // currently the only one using remove any counter using a type uses p1p1 + // the first things are benefit from removing counters - // try to remove +1/+1 counter from undying creature - List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1, c), - CardPredicates.hasKeyword("Undying")); - - if (!prefs.isEmpty()) { - Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.P1P1)); - PaymentDecision result = PaymentDecision.card(prefs); - result.ct = CounterType.get(CounterEnumType.P1P1); - return result; - } - // try to remove -1/-1 counter from persist creature - prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1, c), - CardPredicates.hasKeyword("Persist")); + if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.M1M1))) { + List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1), CardPredicates.hasKeyword(Keyword.PERSIST)); - if (!prefs.isEmpty()) { - Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.M1M1)); - PaymentDecision result = PaymentDecision.card(prefs); - result.ct = CounterType.get(CounterEnumType.M1M1); - return result; + toRemove += removeCounter(table, prefs, CounterEnumType.M1M1, c - toRemove); } - // try to remove Time counter from Chronozoa, it will generate more - prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME, c), - CardPredicates.nameEquals("Chronozoa")); + // try to remove +1/+1 counter from undying creature + if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.P1P1))) { + List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1), CardPredicates.hasKeyword(Keyword.UNDYING)); - if (!prefs.isEmpty()) { - Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.TIME)); - PaymentDecision result = PaymentDecision.card(prefs); - result.ct = CounterType.get(CounterEnumType.TIME); - return result; + toRemove += removeCounter(table, prefs, CounterEnumType.P1P1, c - toRemove); + } + + if (c > toRemove && cost.counter == null && originalHost.hasSVar("AIRemoveCounterCostPriority") && !"ANY".equalsIgnoreCase(originalHost.getSVar("AIRemoveCounterCostPriority"))) { + String[] counters = TextUtil.split(originalHost.getSVar("AIRemoveCounterCostPriority"), ','); + + for (final String ctr : counters) { + CounterType ctype = CounterType.getType(ctr); + // ctype == null means any type + // any type is just used to return null for this + + for (Card card : CardLists.filter(typeList, CardPredicates.hasCounter(ctype))) { + int thisRemove = Math.min(card.getCounters(ctype), c - toRemove); + if (thisRemove > 0) { + toRemove += thisRemove; + table.put(card, ctype, thisRemove); + } + } + } + } + + // filter for negative counters + if (c > toRemove && cost.counter == null) { + List negatives = CardLists.filter(typeList, new Predicate() { + @Override + public boolean apply(final Card crd) { + for (CounterType cType : table.filterToRemove(crd).keySet()) { + if (ComputerUtil.isNegativeCounter(cType, crd)) { + return true; + } + } + return false; + } + }); + + if (!negatives.isEmpty()) { + // TODO sort negatives to remove from best Cards first? + for (final Card crd : negatives) { + for (Map.Entry e : table.filterToRemove(crd).entrySet()) { + if (ComputerUtil.isNegativeCounter(e.getKey(), crd)) { + int over = Math.min(e.getValue(), c - toRemove); + if (over > 0) { + toRemove += over; + table.put(crd, e.getKey(), over); + } + } + } + } + } + } + + // filter for useless counters + // they have no effect on the card, if they are there or removed + if (c > toRemove && cost.counter == null) { + List useless = CardLists.filter(typeList, new Predicate() { + @Override + public boolean apply(final Card crd) { + for (CounterType ctype : table.filterToRemove(crd).keySet()) { + if (ComputerUtil.isUselessCounter(ctype, crd)) { + return true; + } + } + return false; + } + }); + + if (!useless.isEmpty()) { + for (final Card crd : useless) { + for (Map.Entry e : table.filterToRemove(crd).entrySet()) { + if (ComputerUtil.isUselessCounter(e.getKey(), crd)) { + int over = Math.min(e.getValue(), c - toRemove); + if (over > 0) { + toRemove += over; + table.put(crd, e.getKey(), over); + } + } + } + } + } + } + + // try to remove Time counter from Chronozoa, it will generate more token + if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.TIME))) { + List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME), CardPredicates.nameEquals("Chronozoa")); + + toRemove += removeCounter(table, prefs, CounterEnumType.TIME, c - toRemove); } // try to remove Quest counter on something with enough counters for the // effect to continue - prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.QUEST, c)); - - if (!prefs.isEmpty()) { - prefs = CardLists.filter(prefs, new Predicate() { + if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.QUEST))) { + List prefs = CardLists.filter(typeList, new Predicate() { @Override public boolean apply(final Card crd) { // a Card without MaxQuestEffect doesn't need any Quest @@ -694,130 +800,65 @@ public class AiCostDecision extends CostDecisionMakerBase { if (crd.hasSVar("MaxQuestEffect")) { e = Integer.parseInt(crd.getSVar("MaxQuestEffect")); } - return crd.getCounters(CounterEnumType.QUEST) >= e + c; + return crd.getCounters(CounterEnumType.QUEST) > e; } }); Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST))); - PaymentDecision result = PaymentDecision.card(prefs); - result.ct = CounterType.get(CounterEnumType.QUEST); - return result; + + for (final Card crd : prefs) { + int e = 0; + if (crd.hasSVar("MaxQuestEffect")) { + e = Integer.parseInt(crd.getSVar("MaxQuestEffect")); + } + int over = Math.min(crd.getCounters(CounterEnumType.QUEST) - e, c - toRemove); + if (over > 0) { + toRemove += over; + table.put(crd, CounterType.get(CounterEnumType.QUEST), over); + } + } } - // filter for only cards with enough counters - typeList = CardLists.filter(typeList, new Predicate() { - @Override - public boolean apply(final Card crd) { - for (Integer i : crd.getCounters().values()) { - if (i >= c) { - return true; + // remove Lore counters from Sagas to keep them longer + if (c > toRemove && (cost.counter == null || cost.counter.is(CounterEnumType.LORE))) { + List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.LORE), CardPredicates.isType("Saga")); + // TODO add Svars and other stuff to keep the Sagas on specific levels + // also add a way for the AI to respond to the last Chapter ability to keep the Saga on the field if wanted + toRemove += removeCounter(table, prefs, CounterEnumType.LORE, c - toRemove); + } + + + // TODO add logic to remove positive counters? + if (c > toRemove && cost.counter != null) { + // TODO add logic for Ooze Flux, should probably try to make a token as big as possible + // without killing own non undying creatures in the process + // the amount of X should probably be tweaked for this + List withCtr = CardLists.filter(typeList, CardPredicates.hasCounter(cost.counter)); + for (Card card : withCtr) { + int thisRemove = Math.min(card.getCounters(cost.counter), c - toRemove); + if (thisRemove > 0) { + toRemove += thisRemove; + table.put(card, cost.counter, thisRemove); + } + } + } + + // Used to not return null + // Special part for CostPriority Any + if (c > toRemove && cost.counter == null && originalHost.hasSVar("AIRemoveCounterCostPriority") && "ANY".equalsIgnoreCase(originalHost.getSVar("AIRemoveCounterCostPriority"))) { + for (Card card : typeList) { + // TODO try not to remove to much positive counters from the same card + for (Map.Entry e : table.filterToRemove(card).entrySet()) { + int thisRemove = Math.min(e.getValue(), c - toRemove); + if (thisRemove > 0) { + toRemove += thisRemove; + table.put(card, e.getKey(), thisRemove); } } - return false; - } - }); - - // nothing with enough counters of any type - if (typeList.isEmpty()) { - return null; - } - - // filter for negative counters - List negatives = CardLists.filter(typeList, new Predicate() { - @Override - public boolean apply(final Card crd) { - for (Map.Entry e : crd.getCounters().entrySet()) { - if (e.getValue() >= c && ComputerUtil.isNegativeCounter(e.getKey(), crd)) { - return true; - } - } - return false; - } - }); - - if (!negatives.isEmpty()) { - final Card card = ComputerUtilCard.getBestAI(negatives); - PaymentDecision result = PaymentDecision.card(card); - - for (Map.Entry e : card.getCounters().entrySet()) { - if (e.getValue() >= c && ComputerUtil.isNegativeCounter(e.getKey(), card)) { - result.ct = e.getKey(); - break; - } - } - return result; - } - - // filter for useless counters - // they have no effect on the card, if they are there or removed - List useless = CardLists.filter(typeList, new Predicate() { - @Override - public boolean apply(final Card crd) { - for (Map.Entry e : crd.getCounters().entrySet()) { - if (e.getValue() >= c && ComputerUtil.isUselessCounter(e.getKey())) { - return true; - } - } - return false; - } - }); - - if (!useless.isEmpty()) { - final Card card = useless.get(0); - PaymentDecision result = PaymentDecision.card(card); - - for (Map.Entry e : card.getCounters().entrySet()) { - if (e.getValue() >= c && ComputerUtil.isUselessCounter(e.getKey())) { - result.ct = e.getKey(); - break; - } - } - return result; - } - - // try a way to pay unless cost - if ("Chisei, Heart of Oceans".equals(ComputerUtilAbility.getAbilitySourceName(ability))) { - final Card card = ComputerUtilCard.getWorstAI(typeList); - PaymentDecision result = PaymentDecision.card(card); - for (Map.Entry e : card.getCounters().entrySet()) { - if (e.getValue() >= c) { - result.ct = e.getKey(); - break; - } - } - return result; - } - - // check if the card defines its own priorities for counter removal as cost - if (source.hasSVar("AIRemoveCounterCostPriority")) { - String[] counters = TextUtil.split(source.getSVar("AIRemoveCounterCostPriority"), ','); - - for (final String ctr : counters) { - List withCtr = CardLists.filter(typeList, new Predicate() { - @Override - public boolean apply(final Card crd) { - for (Map.Entry e : crd.getCounters().entrySet()) { - if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) { - return true; - } - } - return false; - } - }); - if (!withCtr.isEmpty()) { - final Card card = withCtr.get(0); - PaymentDecision result = PaymentDecision.card(card); - - for (Map.Entry e : card.getCounters().entrySet()) { - if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) { - result.ct = e.getKey(); - break; - } - } - return result; - } } } - return null; + + // if table is empty, than no counter was removed + return table.isEmpty() ? null : PaymentDecision.counters(table); } @Override @@ -843,7 +884,7 @@ public class AiCostDecision extends CostDecisionMakerBase { } } } else if (sVar.equals("Count$xPaid")) { - c = AbilityUtils.calculateAmount(source, "PayX", null); + c = AbilityUtils.calculateAmount(source, "PayX", ability); } else { c = AbilityUtils.calculateAmount(source, amount, ability); } @@ -898,7 +939,7 @@ public class AiCostDecision extends CostDecisionMakerBase { System.out.println("Couldn't find a valid card to untap for: " + source.getName()); return null; } - + return PaymentDecision.card(list); } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 29ca893d82f..8a8a4734bed 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -161,8 +161,6 @@ public class ComputerUtil { // Play higher costing spells first? final Cost cost = sa.getPayCosts(); - // Convert cost to CMC - // String totalMana = source.getSVar("PayX"); // + cost.getCMC() // Consider the costs here for relative "scoring" if (hasDiscardHandCost(cost)) { @@ -2809,7 +2807,17 @@ public class ComputerUtil { } // this countertypes has no effect - public static boolean isUselessCounter(CounterType type) { + public static boolean isUselessCounter(CounterType type, Card c) { + + // Quest counter on a card without MaxQuestEffect are useless + if (type.is(CounterEnumType.QUEST)) { + int e = 0; + if ( c.hasSVar("MaxQuestEffect")) { + e = Integer.parseInt(c.getSVar("MaxQuestEffect")); + } + return c.getCounters(type) > e; + } + return type.is(CounterEnumType.AWAKENING) || type.is(CounterEnumType.MANIFESTATION) || type.is(CounterEnumType.PETRIFICATION) || type.is(CounterEnumType.TRAINING); } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index ccf63277425..c5e428cd87d 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -19,6 +19,8 @@ import forge.game.zone.ZoneType; import forge.util.MyRandom; import forge.util.TextUtil; import forge.util.collect.FCollectionView; + +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.util.List; @@ -53,9 +55,6 @@ public class ComputerUtilCost { return true; } - public static boolean checkRemoveCounterCost(final Cost cost, final Card source) { - return checkRemoveCounterCost(cost, source, null); - } /** * Check remove counter cost. * @@ -69,6 +68,7 @@ public class ComputerUtilCost { if (cost == null) { return true; } + final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa); for (final CostPart part : cost.getCostParts()) { if (part instanceof CostRemoveCounter) { final CostRemoveCounter remCounter = (CostRemoveCounter) part; @@ -88,17 +88,17 @@ public class ComputerUtilCost { // Remove X counters - set ChosenX to max possible value here, the SAs should correct that // value later as the AI decides what to do (in checkApiLogic / checkAiLogic) - if (sa != null && sa.hasSVar(remCounter.getAmount())) { + if (sa.hasSVar(remCounter.getAmount())) { final String sVar = sa.getSVar(remCounter.getAmount()); if (sVar.equals("XChoice") && !sa.hasSVar("ChosenX")) { sa.setSVar("ChosenX", String.valueOf(source.getCounters(type))); + } else if (sVar.equals("Count$xPaid") && sa.hasSVar("PayX")) { + sa.setSVar("PayX", Integer.toString(Math.min(Integer.valueOf(sa.getSVar("PayX")), source.getCounters(type)))); } } - // check the sa what the PaymentDecision is. // ignore Loyality abilities with Zero as Cost - if (sa != null && !type.is(CounterEnumType.LOYALTY)) { - final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa); + if (!type.is(CounterEnumType.LOYALTY)) { PaymentDecision pay = decision.visit(remCounter); if (pay == null || pay.c <= 0) { return false; @@ -111,14 +111,10 @@ public class ComputerUtilCost { return false; } } else if (part instanceof CostRemoveAnyCounter) { - if (sa != null) { - final CostRemoveAnyCounter remCounter = (CostRemoveAnyCounter) part; + final CostRemoveAnyCounter remCounter = (CostRemoveAnyCounter) part; - PaymentDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa).visit(remCounter); - return decision != null; - } - - return false; + PaymentDecision pay = decision.visit(remCounter); + return pay != null; } } return true; @@ -677,4 +673,26 @@ public class ComputerUtilCost { } return false; } + + public static int getMaxXValue(SpellAbility sa, Player ai) { + final Cost abCost = sa.getPayCosts(); + if (abCost == null || !abCost.hasXInAnyCostPart()) { + return 0; + } + + Integer val = null; + + if (sa.costHasManaX()) { + val = ComputerUtilMana.determineLeftoverMana(sa, ai); + } + + if (sa.usesTargeting()) { + // if announce is used as min targets, check what the max possible number would be + if ("X".equals(sa.getTargetRestrictions().getMinTargets())) { + val = ObjectUtils.min(val, CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa).size()); + } + } + + return ObjectUtils.defaultIfNull(ObjectUtils.min(val, abCost.getMaxForNonManaX(sa, ai)), 0); + } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index d0685de143f..cda5239a2b1 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -4,7 +4,6 @@ import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.*; import forge.ai.ability.AnimateAi; -import forge.card.CardStateName; import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaAtom; @@ -119,10 +118,6 @@ public class ComputerUtilMana { return score; } - private static void sortManaAbilities(final Multimap manaAbilityMap) { - sortManaAbilities(manaAbilityMap, null); - } - private static void sortManaAbilities(final Multimap manaAbilityMap, final SpellAbility sa) { final Map manaCardMap = Maps.newHashMap(); final List orderedCards = Lists.newArrayList(); @@ -1194,11 +1189,11 @@ public class ComputerUtilMana { } else { // For Count$xPaid set PayX in the AFs then use that here // Else calculate it as appropriate. - final String xSvar = card.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X"; - if (!sa.getSVar(xSvar).isEmpty() || card.hasSVar(xSvar) || card.getState(CardStateName.Original).hasSVar(xSvar)) { - if (xSvar.equals("PayX") && (card.hasSVar(xSvar) || card.getState(CardStateName.Original).hasSVar(xSvar))) { + final String xSvar = sa.getSVar("X").startsWith("Count$xPaid") ? "PayX" : "X"; + if (sa.hasSVar(xSvar)) { + if (xSvar.equals("PayX")) { // X SVar may end up being an empty string when copying a spell with no cost (e.g. Jhoira Avatar) - String xValue = card.hasSVar(xSvar) ? card.getSVar(xSvar) : card.getState(CardStateName.Original).getSVar(xSvar); + String xValue = sa.getSVar(xSvar); manaToAdd = xValue.isEmpty() ? 0 : Integer.parseInt(xValue) * cost.getXcounter(); // X } else { manaToAdd = AbilityUtils.calculateAmount(card, xSvar, sa) * cost.getXcounter(); @@ -1206,9 +1201,7 @@ public class ComputerUtilMana { } } - String manaXColor = sa.getParam("XColor"); - ManaCostShard shardToGrow = ManaCostShard.parseNonGeneric(manaXColor == null ? "1" : manaXColor); - cost.increaseShard(shardToGrow, manaToAdd); + cost.increaseShard(ManaCostShard.parseNonGeneric(sa.getParamOrDefault("XColor", "1")), manaToAdd); if (!test) { sa.setXManaCostPaid(manaToAdd / cost.getXcounter()); diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index f5932462c3a..96978de7320 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -875,7 +875,7 @@ public class SpecialCardAi { tokenSize = 11; } - source.setSVar("PayX", Integer.toString(tokenSize)); + sa.setSVar("PayX", Integer.toString(tokenSize)); return true; } diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java index 665e8881f4f..6b78b69b223 100644 --- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java @@ -78,12 +78,6 @@ public abstract class SpellAbilityAi { } } - if (sa.hasParam("AITgtBeforeCostEval")) { - // Cost payment requires a valid target to be specified, e.g. Quillmane Baku, so run the API logic first - // to set the target, then decide on paying costs (slower, so only use for cards where it matters) - return checkApiLogic(ai, sa) && (cost == null || willPayCosts(ai, sa, cost, source)); - } - if (cost != null && !willPayCosts(ai, sa, cost, source)) { 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 5473029db9f..09624b037d4 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -93,7 +93,7 @@ public class AttachAi extends SpellAbilityAi { return false; } - if (abCost.getTotalMana().countX() > 0 && source.getSVar("X").equals("Count$xPaid")) { + 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 = ComputerUtilMana.determineLeftoverMana(sa, ai); @@ -102,7 +102,7 @@ public class AttachAi extends SpellAbilityAi { return false; } - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Chained to the Rocks")) { 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 12f5966d29e..3ca3777d814 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -339,10 +339,10 @@ public class ChangeZoneAi extends SpellAbilityAi { String type = sa.getParam("ChangeType"); if (type != null) { - if (type.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); type = type.replace("X", Integer.toString(xPay)); } } @@ -384,11 +384,11 @@ public class ChangeZoneAi extends SpellAbilityAi { String num = sa.getParam("ChangeNum"); if (num != null) { - if (num.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (num.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); xPay = Math.min(xPay, list.size()); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } } @@ -474,8 +474,6 @@ public class ChangeZoneAi extends SpellAbilityAi { // Fetching should occur fairly often as it helps cast more spells, and // have access to more mana - final Card source = sa.getHostCard(); - if (sa.hasParam("AILogic")) { if (sa.getParam("AILogic").equals("Never")) { /* @@ -496,10 +494,10 @@ public class ChangeZoneAi extends SpellAbilityAi { // this works for hidden because the mana is paid first. final String type = sa.getParam("ChangeType"); - if (type != null && type.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (type != null && type.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } Iterable pDefined; @@ -1830,7 +1828,7 @@ public class ChangeZoneAi extends SpellAbilityAi { int toPay = 0; boolean setPayX = false; - if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) { + if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) { setPayX = true; toPay = ComputerUtilMana.determineLeftoverMana(sa, ai); } else { @@ -1846,7 +1844,7 @@ public class ChangeZoneAi extends SpellAbilityAi { } if (setPayX) { - source.setSVar("PayX", Integer.toString(toPay)); + sa.setSVar("PayX", Integer.toString(toPay)); } } } 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 e086a64803e..cefb6cfe3db 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 { } // Set PayX here to maximum value. int x = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(x)); + sa.setSVar("PayX", Integer.toString(x)); return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java index 0f2b2730321..7781490d425 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java @@ -60,7 +60,7 @@ public class ChooseSourceAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { + if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) { return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java index 7c14df50863..2e4a66fbcef 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java @@ -280,7 +280,7 @@ public class ControlGainAi extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, final Player ai) { final Game game = ai.getGame(); - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting()) { if (sa.hasParam("AllValid")) { CardCollectionView tgtCards = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); tgtCards = AbilityUtils.filterListByType(tgtCards, sa.getParam("AllValid"), sa); 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 41edaa0d49a..a60fbe69051 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java @@ -103,7 +103,7 @@ public class CounterAi extends SpellAbilityAi { int toPay = 0; boolean setPayX = false; - if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) { + if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) { setPayX = true; toPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), usableManaSources + 1); } else { @@ -123,7 +123,7 @@ public class CounterAi extends SpellAbilityAi { } if (setPayX) { - source.setSVar("PayX", Integer.toString(toPay)); + sa.setSVar("PayX", Integer.toString(toPay)); } } @@ -267,7 +267,7 @@ public class CounterAi extends SpellAbilityAi { int toPay = 0; boolean setPayX = false; - if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) { + if (unlessCost.equals("X") && sa.getSVar(unlessCost).equals("Count$xPaid")) { setPayX = true; toPay = ComputerUtilMana.determineLeftoverMana(sa, ai); } else { @@ -289,7 +289,7 @@ public class CounterAi extends SpellAbilityAi { } if (setPayX) { - source.setSVar("PayX", Integer.toString(toPay)); + sa.setSVar("PayX", Integer.toString(toPay)); } } } 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 e1d76e9c0db..8de73d6a071 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -338,7 +338,7 @@ public class CountersPutAi extends SpellAbilityAi { } if (amountStr.equals("X")) { - if (source.getSVar(amountStr).equals("Count$xPaid")) { + if (sa.getSVar(amountStr).equals("Count$xPaid")) { // By default, set PayX here to maximum value (used for most SAs of this type). amount = ComputerUtilMana.determineLeftoverMana(sa, ai); @@ -359,7 +359,7 @@ public class CountersPutAi extends SpellAbilityAi { } } - source.setSVar("PayX", Integer.toString(amount)); + sa.setSVar("PayX", Integer.toString(amount)); } else if ("ExiledCreatureFromGraveCMC".equals(logic)) { // e.g. Necropolis amount = Aggregates.max(CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.Presets.CREATURES), CardPredicates.Accessors.fnGetCmc); @@ -698,8 +698,8 @@ public class CountersPutAi extends SpellAbilityAi { list = new CardCollection(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)); if (amountStr.equals("X") - && !source.hasSVar("PayX") /* SubAbility on something that already had set PayX, e.g. Endless One ETB counters */ - && ((sa.hasParam(amountStr) && sa.getSVar(amountStr).equals("Count$xPaid")) || source.getSVar(amountStr).equals("Count$xPaid") )) { + && !sa.hasSVar("PayX") /* SubAbility on something that already had set PayX, e.g. Endless One ETB counters */ + && ((sa.hasParam(amountStr) && sa.getSVar(amountStr).equals("Count$xPaid")))) { // detect if there's more than one X in the cost (Hangarback Walker, Walking Ballista, etc.) SpellAbility testSa = sa; @@ -725,7 +725,7 @@ public class CountersPutAi extends SpellAbilityAi { // Account for the multiple X in cost if (countX > 1) { payX /= countX; } - source.setSVar("PayX", Integer.toString(payX)); + sa.setSVar("PayX", Integer.toString(payX)); } if (!mandatory) { @@ -1031,7 +1031,7 @@ public class CountersPutAi extends SpellAbilityAi { } } else { for (CounterType type : options) { - if (!ComputerUtil.isNegativeCounter(type, c) && !ComputerUtil.isUselessCounter(type)) { + if (!ComputerUtil.isNegativeCounter(type, c) && !ComputerUtil.isUselessCounter(type, c)) { return type; } } 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 8c883637620..4fc300b6c1b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java @@ -82,10 +82,10 @@ public class CountersPutAllAi extends SpellAbilityAi { // TODO improve X value to don't overpay when extra mana won't do // anything more useful final int amount; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(amount)); + sa.setSVar("PayX", Integer.toString(amount)); } else { amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java index a261270dec9..70e66e15c59 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java @@ -157,7 +157,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi { if (!ComputerUtil.isNegativeCounter(aType, best)) { sa.getTargets().add(best); return true; - } else if (!ComputerUtil.isUselessCounter(aType)) { + } else if (!ComputerUtil.isUselessCounter(aType, best)) { // whould remove positive counter if (best.getCounters(aType) <= amount) { sa.getTargets().add(best); 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 1d4737a83ea..8ce1b1a369c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java @@ -143,7 +143,7 @@ public class CountersRemoveAi extends SpellAbilityAi { // variable amount for Hex Parasite int amount; boolean xPay = false; - if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) { final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai); if (manaLeft == 0) { @@ -167,7 +167,7 @@ public class CountersRemoveAi extends SpellAbilityAi { if (amount >= ice) { sa.getTargets().add(depth); if (xPay) { - source.setSVar("PayX", Integer.toString(ice)); + sa.setSVar("PayX", Integer.toString(ice)); } return true; } @@ -186,7 +186,7 @@ public class CountersRemoveAi extends SpellAbilityAi { Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList); sa.getTargets().add(best); if (xPay) { - source.setSVar("PayX", Integer.toString(best.getCurrentLoyalty())); + sa.setSVar("PayX", Integer.toString(best.getCurrentLoyalty())); } return true; } @@ -297,7 +297,7 @@ public class CountersRemoveAi extends SpellAbilityAi { int amount; boolean xPay = false; // Timecrafting has X R - if (amountStr.equals("X") && source.getSVar("X").equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar("X").equals("Count$xPaid")) { final int manaLeft = ComputerUtilMana.determineLeftoverMana(sa, ai); if (manaLeft == 0) { @@ -317,7 +317,7 @@ public class CountersRemoveAi extends SpellAbilityAi { int timeCount = best.getCounters(CounterEnumType.TIME); sa.getTargets().add(best); if (xPay) { - source.setSVar("PayX", Integer.toString(timeCount)); + sa.setSVar("PayX", Integer.toString(timeCount)); } return true; } 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 79e83ac65a4..15e5c05c1a9 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java @@ -83,7 +83,7 @@ public class DamageAllAi extends SpellAbilityAi { if (best_x > 0) { if (sa.getSVar(damage).equals("Count$xPaid")) { - source.setSVar("PayX", Integer.toString(best_x)); + sa.setSVar("PayX", Integer.toString(best_x)); } if (damage.equals("ChosenX")) { source.setSVar("ChosenX", "Number$" + best_x); @@ -203,7 +203,7 @@ public class DamageAllAi extends SpellAbilityAi { if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) { // Set PayX here to maximum value. dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(dmg)); + sa.setSVar("PayX", Integer.toString(dmg)); } if (sa.hasParam("ValidPlayers")) { @@ -285,7 +285,7 @@ public class DamageAllAi extends SpellAbilityAi { if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) { // Set PayX here to maximum value. dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(dmg)); + sa.setSVar("PayX", Integer.toString(dmg)); } if (sa.hasParam("ValidPlayers")) { 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 92c213ca2e3..20bbb1a8e5a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -12,7 +12,6 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.cost.Cost; -import forge.game.cost.CostPart; import forge.game.cost.CostPartMana; import forge.game.cost.CostRemoveCounter; import forge.game.keyword.Keyword; @@ -75,8 +74,8 @@ public class DamageDealAi extends DamageAiBase { } // Set PayX here to maximum value. - dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(dmg)); + dmg = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(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 } @@ -96,7 +95,7 @@ public class DamageDealAi extends DamageAiBase { if (damage.equals("X")) { if (sa.getSVar(damage).equals("Count$xPaid") || sourceName.equals("Crater's Claws")) { - dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); + dmg = ComputerUtilCost.getMaxXValue(sa, ai); // 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()) { @@ -113,7 +112,7 @@ public class DamageDealAi extends DamageAiBase { } // Set PayX here to maximum value. It will be adjusted later depending on the target. - source.setSVar("PayX", Integer.toString(dmg)); + sa.setSVar("PayX", Integer.toString(dmg)); } else if (sa.getSVar(damage).contains("InYourHand") && source.isInZone(ZoneType.Hand)) { dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less } else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) { @@ -225,7 +224,7 @@ public class DamageDealAi extends DamageAiBase { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { + if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) { return false; } @@ -266,7 +265,7 @@ public class DamageDealAi extends DamageAiBase { } } - if ((damage.equals("X") && source.getSVar(damage).equals("Count$xPaid")) || + if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) || sourceName.equals("Crater's Claws")){ // If I can kill my target by paying less mana, do it if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) { @@ -281,25 +280,13 @@ public class DamageDealAi extends DamageAiBase { if (sourceName.equals("Crater's Claws") && ai.hasFerocious()) { actualPay = actualPay > 2 ? actualPay - 2 : 0; } - source.setSVar("PayX", Integer.toString(actualPay)); - } - } - - if ("XCountersDamage".equals(logic)) { - // Check to ensure that we have enough counters to remove per the defined PayX - for (CostPart part : sa.getPayCosts().getCostParts()) { - if (part instanceof CostRemoveCounter) { - if (source.getCounters(((CostRemoveCounter) part).counter) < Integer.valueOf(source.getSVar("PayX"))) { - return false; - } - break; - } + sa.setSVar("PayX", Integer.toString(actualPay)); } } if ("DiscardCMCX".equals(sa.getParam("AILogic"))) { - final int CMC = Integer.parseInt(source.getSVar("PayX")); - return !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(CMC)).isEmpty(); + final int cmc = Integer.parseInt(sa.getSVar("PayX")); + return !CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.hasCMC(cmc)).isEmpty(); } return true; @@ -990,7 +977,7 @@ public class DamageDealAi extends DamageAiBase { if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) { // Set PayX here to maximum value. dmg = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(dmg)); + sa.setSVar("PayX", Integer.toString(dmg)); } final TargetRestrictions tgt = sa.getTargetRestrictions(); @@ -1002,7 +989,7 @@ public class DamageDealAi extends DamageAiBase { return false; } - if (damage.equals("X") && source.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) { + if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) { // If I can kill my target by paying less mana, do it int actualPay = 0; final boolean noPrevention = sa.hasParam("NoPrevention"); @@ -1018,7 +1005,7 @@ public class DamageDealAi extends DamageAiBase { } } - source.setSVar("PayX", Integer.toString(actualPay)); + sa.setSVar("PayX", Integer.toString(actualPay)); } } @@ -1078,7 +1065,7 @@ public class DamageDealAi extends DamageAiBase { saTgt.resetTargets(); saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent); - source.setSVar("PayX", Integer.toString(dmg)); + sa.setSVar("PayX", Integer.toString(dmg)); return true; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java index 2a171dbbb07..6e5259f25cb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java @@ -46,7 +46,7 @@ public class DamagePreventAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) { + if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAllAi.java index f6f8b73f2e2..f62a7b51c25 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAllAi.java @@ -34,7 +34,7 @@ public class DamagePreventAllAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) { + if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java b/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java index 3d7b045108f..b41018c9d35 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java @@ -50,7 +50,7 @@ public class DebuffAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) { + if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) { return false; } @@ -67,7 +67,7 @@ public class DebuffAi extends SpellAbilityAi { } } - if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting()) { List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); @@ -93,7 +93,7 @@ public class DebuffAi extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, Player ai) { - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting()) { // TODO - copied from AF_Pump.pumpDrawbackAI() - what should be // here? } else { 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 dc0093e7a90..33108116b0c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java @@ -48,7 +48,7 @@ public class DestroyAi extends SpellAbilityAi { return false; } - hasXCost = abCost.getCostMana() != null && abCost.getCostMana().getAmountOfX() > 0; + hasXCost = sa.costHasManaX(); } if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) { 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 ba3b9e45afd..9fbe5427ccf 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java @@ -80,10 +80,10 @@ public class DestroyAllAi extends SpellAbilityAi { valid = sa.getParam("ValidCards"); } - if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(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 822423e4f48..d7aaed7d3ab 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigAi.java @@ -75,9 +75,9 @@ public class DigAi extends SpellAbilityAi { final String num = sa.getParam("DigNum"); final boolean payXLogic = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("PayX"); - if (num != null && (num.equals("X") && host.getSVar(num).equals("Count$xPaid")) || payXLogic) { + if (num != null && (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) || payXLogic) { // By default, set PayX here to maximum value. - if (!(sa instanceof AbilitySub) || host.getSVar("PayX").equals("")) { + if (!(sa instanceof AbilitySub) || sa.getSVar("PayX").equals("")) { int manaToSave = 0; // Special logic that asks the AI to conserve a certain amount of mana when paying X @@ -89,7 +89,7 @@ public class DigAi extends SpellAbilityAi { if (numCards <= 0) { return false; } - host.setSVar("PayX", Integer.toString(numCards)); + sa.setSVar("PayX", Integer.toString(numCards)); } } 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 f18dc3c7784..5eb12be3a17 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java @@ -76,14 +76,14 @@ public class DigUntilAi extends SpellAbilityAi { } final String num = sa.getParam("Amount"); - if ((num != null) && num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { + if ((num != null) && num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. - if (!(sa instanceof AbilitySub) || source.getSVar("PayX").equals("")) { + if (!(sa instanceof AbilitySub) || sa.getSVar("PayX").equals("")) { int numCards = ComputerUtilMana.determineLeftoverMana(sa, ai); if (numCards <= 0) { return false; } - source.setSVar("PayX", Integer.toString(numCards)); + sa.setSVar("PayX", Integer.toString(numCards)); } } 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 d7dad6b293d..8bac65be1b4 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java @@ -41,7 +41,7 @@ public class DiscardAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) { + if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) { return false; } @@ -85,14 +85,14 @@ public class DiscardAi extends SpellAbilityAi { } if (sa.hasParam("NumCards")) { - if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) { + if (sa.getParam("NumCards").equals("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int cardsToDiscard = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), ai.getWeakestOpponent() .getCardsIn(ZoneType.Hand).size()); if (cardsToDiscard < 1) { return false; } - source.setSVar("PayX", Integer.toString(cardsToDiscard)); + sa.setSVar("PayX", Integer.toString(cardsToDiscard)); } else { if (AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumCards"), sa) < 1) { return false; 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 4047d12ee77..579d731d625 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -238,17 +238,17 @@ public class DrawAi extends SpellAbilityAi { boolean xPaid = false; final String num = sa.getParam("NumCards"); if (num != null && num.equals("X")) { - if (source.getSVar(num).equals("Count$xPaid")) { + if (sa.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. - if (drawback && !source.getSVar("PayX").equals("")) { - numCards = Integer.parseInt(source.getSVar("PayX")); + if (drawback && !sa.getSVar("PayX").equals("")) { + numCards = Integer.parseInt(sa.getSVar("PayX")); } else { numCards = ComputerUtilMana.determineLeftoverMana(sa, ai); // try not to overdraw int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3); if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent numCards = Math.min(numCards, safeDraw); - source.setSVar("PayX", Integer.toString(numCards)); + sa.setSVar("PayX", Integer.toString(numCards)); assumeSafeX = true; } xPaid = true; @@ -340,7 +340,7 @@ public class DrawAi extends SpellAbilityAi { // for drawing and losing life if (numCards >= oppA.getLife()) { if (xPaid) { - source.setSVar("PayX", Integer.toString(oppA.getLife())); + sa.setSVar("PayX", Integer.toString(oppA.getLife())); } sa.getTargets().add(oppA); return true; @@ -400,7 +400,7 @@ public class DrawAi extends SpellAbilityAi { } if (xPaid) { - source.setSVar("PayX", Integer.toString(numCards)); + sa.setSVar("PayX", Integer.toString(numCards)); } } @@ -411,7 +411,7 @@ public class DrawAi extends SpellAbilityAi { if (sa.getHostCard().isInZone(ZoneType.Hand)) { numCards++; // the card will be spent } - source.setSVar("PayX", Integer.toString(numCards)); + sa.setSVar("PayX", Integer.toString(numCards)); } else { // Don't draw too many cards and then risk discarding // cards at EOT 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 5ad2c283477..359490e75fd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java @@ -49,7 +49,7 @@ public class LifeGainAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(cost, source)) { + if (!ComputerUtilCost.checkRemoveCounterCost(cost, source, sa)) { return false; } } else { @@ -125,10 +125,10 @@ public class LifeGainAi extends SpellAbilityAi { final String amountStr = sa.getParam("LifeAmount"); int lifeAmount = 0; boolean activateForCost = ComputerUtil.activateForCost(sa, ai); - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); lifeAmount = xPay; } else { lifeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); @@ -213,12 +213,11 @@ public class LifeGainAi extends SpellAbilityAi { } } - final Card source = sa.getHostCard(); final String amountStr = sa.getParam("LifeAmount"); - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } return true; 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 23ee0a4712e..de4bf22b32d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java @@ -34,14 +34,14 @@ public class LifeLoseAi extends SpellAbilityAi { final Card source = sa.getHostCard(); final String amountStr = sa.getParam("LifeAmount"); int amount = 0; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // something already set PayX - if (source.hasSVar("PayX")) { - amount = Integer.parseInt(source.getSVar("PayX")); + if (sa.hasSVar("PayX")) { + amount = Integer.parseInt(sa.getSVar("PayX")); } else { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } } else { @@ -72,10 +72,9 @@ public class LifeLoseAi extends SpellAbilityAi { final String amountStr = sa.getParam("LifeAmount"); int amount = 0; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = ComputerUtilMana.determineLeftoverMana(sa, ai); - // source.setSVar("PayX", Integer.toString(amount)); } else { amount = AbilityUtils.calculateAmount(source, amountStr, sa); } @@ -101,10 +100,10 @@ public class LifeLoseAi extends SpellAbilityAi { final String amountStr = sa.getParam("LifeAmount"); int amount = 0; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(amount)); + sa.setSVar("PayX", Integer.toString(amount)); } else { amount = AbilityUtils.calculateAmount(source, amountStr, sa); } @@ -172,10 +171,10 @@ public class LifeLoseAi extends SpellAbilityAi { final Card source = sa.getHostCard(); final String amountStr = sa.getParam("LifeAmount"); int amount = 0; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } else { amount = AbilityUtils.calculateAmount(source, amountStr, sa); 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 2afc4b47137..d51395a7026 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java @@ -16,8 +16,6 @@ public class LifeSetAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { - // Ability_Cost abCost = sa.getPayCosts(); - final Card source = sa.getHostCard(); final int myLife = ai.getLife(); final Player opponent = ai.getWeakestOpponent(); final int hlife = opponent.getLife(); @@ -42,10 +40,10 @@ public class LifeSetAi extends SpellAbilityAi { // would be paid int amount; // we shouldn't have to worry too much about PayX for SetLife - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } else { amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); @@ -114,10 +112,10 @@ public class LifeSetAi extends SpellAbilityAi { final String amountStr = sa.getParam("LifeAmount"); int amount; - if (amountStr.equals("X") && source.getSVar(amountStr).equals("Count$xPaid")) { + if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); amount = xPay; } else { amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); 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 b3d205c4958..b6608c17988 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java @@ -80,7 +80,7 @@ public class ManifestAi extends SpellAbilityAi { // Handle either Manifest X cards, or Manifest 1 card and give it X P1P1s // Set PayX here to maximum value. int x = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(x)); + sa.setSVar("PayX", Integer.toString(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 52f0e8cda25..a469cc108be 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MillAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MillAi.java @@ -75,7 +75,6 @@ public class MillAi extends SpellAbilityAi { * - check for Laboratory Maniac effect (needs to check for actual * effect due to possibility of "lose abilities" effect) */ - final Card source = sa.getHostCard(); if (ComputerUtil.preventRunAwayActivations(sa)) { return false; // prevents mill 0 infinite loop? } @@ -90,10 +89,10 @@ public class MillAi extends SpellAbilityAi { } if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z")) - && source.getSVar("X").startsWith("Count$xPaid")) { + && sa.getSVar("X").startsWith("Count$xPaid")) { // Set PayX here to maximum value. final int cardsToDiscard = getNumToDiscard(ai, sa); - source.setSVar("PayX", Integer.toString(cardsToDiscard)); + sa.setSVar("PayX", Integer.toString(cardsToDiscard)); return cardsToDiscard > 0; } return true; @@ -182,11 +181,10 @@ public class MillAi extends SpellAbilityAi { return false; } - final Card source = sa.getHostCard(); - if (sa.getParam("NumCards").equals("X") && source.getSVar("X").equals("Count$xPaid")) { + if (sa.getParam("NumCards").equals("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int cardsToDiscard = getNumToDiscard(aiPlayer, sa); - source.setSVar("PayX", Integer.toString(cardsToDiscard)); + sa.setSVar("PayX", Integer.toString(cardsToDiscard)); } return true; diff --git a/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java b/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java index cf9ecdf6555..9557f3204eb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java @@ -164,7 +164,7 @@ public class ProtectAi extends SpellAbilityAi { @Override protected boolean checkApiLogic(final Player ai, final SpellAbility sa) { - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting()) { final List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); if (cards.size() == 0) { return false; @@ -359,12 +359,7 @@ public class ProtectAi extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, Player ai) { - final Card host = sa.getHostCard(); - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { - if (host.isCreature()) { - // TODO - } - } else { + if (sa.usesTargeting()) { return protectTgtAI(ai, sa, false); } diff --git a/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java b/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java index 7fb827cd033..8e60b3d397c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java @@ -33,7 +33,7 @@ public class ProtectAllAi extends SpellAbilityAi { return false; } - if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard)) { + if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) { return false; } 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 9d4ec101c8e..2da1bfb5662 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -10,8 +10,6 @@ import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.card.CardPredicates.Presets; import forge.game.cost.Cost; -import forge.game.cost.CostPart; -import forge.game.cost.CostRemoveCounter; import forge.game.cost.CostTapType; import forge.game.keyword.Keyword; import forge.game.phase.PhaseHandler; @@ -288,19 +286,19 @@ public class PumpAi extends PumpAiBase { } } - if (source.getSVar("X").equals("Count$xPaid")) { - source.setSVar("PayX", ""); + if (sa.getSVar("X").equals("Count$xPaid")) { + sa.setSVar("PayX", ""); } int defense; - if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. - int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); + int xPay = ComputerUtilCost.getMaxXValue(sa, ai); if (sourceName.equals("Necropolis Fiend")) { xPay = Math.min(xPay, sa.getActivatingPlayer().getCardsIn(ZoneType.Graveyard).size()); sa.setSVar("X", Integer.toString(xPay)); } - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); defense = xPay; if (numDefense.equals("-X")) { defense = -xPay; @@ -313,13 +311,13 @@ public class PumpAi extends PumpAiBase { } int attack; - if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. - final String toPay = source.getSVar("PayX"); + final String toPay = sa.getSVar("PayX"); if (toPay.equals("")) { - final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + final int xPay = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(xPay)); attack = xPay; } else { attack = Integer.parseInt(toPay); @@ -353,7 +351,7 @@ public class PumpAi extends PumpAiBase { } //Untargeted - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting()) { final List cards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); if (cards.isEmpty()) { @@ -398,19 +396,10 @@ public class PumpAi extends PumpAiBase { return false; } - if ("DebuffForXCounters".equals(sa.getParam("AILogic")) && sa.getTargetCard() != null) { - // e.g. Skullmane Baku - CounterType ctrType = CounterType.get(CounterEnumType.KI); - for (CostPart part : sa.getPayCosts().getCostParts()) { - if (part instanceof CostRemoveCounter) { - ctrType = ((CostRemoveCounter)part).counter; - break; - } - } - + if (!sa.getSVar("PayX").isEmpty() && sa.getTargetCard() != null) { // Do not pay more counters than necessary to kill the targeted creature - int chosenX = Math.min(source.getCounters(ctrType), sa.getTargetCard().getNetToughness()); - sa.setSVar("ChosenX", String.valueOf(chosenX)); + int chosenX = Math.min(sa.getSVarInt("PayX"), sa.getTargetCard().getNetToughness()); + sa.setSVar("PayX", String.valueOf(chosenX)); } return true; @@ -573,12 +562,12 @@ public class PumpAi extends PumpAiBase { } } - while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) { + while (sa.canAddMoreTarget()) { Card t = null; // boolean goodt = false; if (list.isEmpty()) { - if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (mandatory || ComputerUtil.activateForCost(sa, ai)) { return pumpMandatoryTarget(ai, sa); } @@ -678,28 +667,27 @@ public class PumpAi extends PumpAiBase { @Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - final Card source = sa.getHostCard(); final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : ""; final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : ""; int defense; - if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. - final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + final int xPay = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(xPay)); defense = xPay; } else { defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa); } int attack; - if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. - final String toPay = source.getSVar("PayX"); + final String toPay = sa.getSVar("PayX"); if (toPay.equals("")) { - final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + final int xPay = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(xPay)); attack = xPay; } else { attack = Integer.parseInt(toPay); @@ -708,7 +696,7 @@ public class PumpAi extends PumpAiBase { attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa); } - if (sa.getTargetRestrictions() == null) { + if (!sa.usesTargeting()) { if (mandatory) { return true; } @@ -749,28 +737,28 @@ public class PumpAi extends PumpAiBase { return false; } - int defense; - if (numDefense.contains("X") && source.getSVar("X").equals("Count$xPaid")) { - defense = Integer.parseInt(source.getSVar("PayX")); - } else { - defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa); - } - int attack; - if (numAttack.contains("X") && source.getSVar("X").equals("Count$xPaid")) { - if (source.getSVar("PayX").equals("")) { + if (numAttack.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { + if (sa.getSVar("PayX").equals("")) { // X is not set yet - final int xPay = ComputerUtilMana.determineLeftoverMana(sa.getRootAbility(), ai); - source.setSVar("PayX", Integer.toString(xPay)); + final int xPay = ComputerUtilCost.getMaxXValue(sa, ai); + sa.setSVar("PayX", Integer.toString(xPay)); attack = xPay; } else { - attack = Integer.parseInt(source.getSVar("PayX")); + attack = Integer.parseInt(sa.getSVar("PayX")); } } else { attack = AbilityUtils.calculateAmount(sa.getHostCard(), numAttack, sa); } - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + int defense; + if (numDefense.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { + defense = Integer.parseInt(sa.getSVar("PayX")); + } else { + defense = AbilityUtils.calculateAmount(sa.getHostCard(), numDefense, sa); + } + + if (!sa.usesTargeting()) { if (source.isCreature()) { if (!source.hasKeyword(Keyword.INDESTRUCTIBLE) && source.getNetToughness() + defense <= source.getDamage()) { return false; 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 7a37733b284..53be584b138 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java @@ -2,22 +2,18 @@ package forge.ai.ability; import forge.ai.*; -import forge.game.card.Card; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; public class RepeatAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { - final Card source = sa.getHostCard(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Player opp = ai.getWeakestOpponent(); - if (tgt != null) { + if (sa.usesTargeting()) { if (!opp.canBeTargetedBy(sa)) { return false; } @@ -31,7 +27,7 @@ public class RepeatAi extends SpellAbilityAi { } // Set PayX here to maximum value. final int max = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(max)); + sa.setSVar("PayX", Integer.toString(max)); return max > 0; } return true; 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 78081283c46..18a2cf4e13e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java @@ -103,10 +103,10 @@ public class SacrificeAi extends SpellAbilityAi { return false; } - if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { + if (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } final int half = (amount / 2) + (amount % 2); // Half of amount @@ -135,7 +135,7 @@ public class SacrificeAi extends SpellAbilityAi { final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1"; int amount = AbilityUtils.calculateAmount(source, num, sa); - if (num.equals("X") && source.getSVar(num).equals("Count$xPaid")) { + if (num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. amount = Math.min(ComputerUtilMana.determineLeftoverMana(sa, ai), amount); } diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java index 8032c127eed..5f0e3de1931 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java @@ -28,10 +28,10 @@ public class SacrificeAllAi extends SpellAbilityAi { valid = sa.getParam("ValidCards"); } - if (valid.contains("X") && source.getSVar("X").equals("Count$xPaid")) { + if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); valid = TextUtil.fastReplace(valid, "X", Integer.toString(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 2abada6610d..b7560a63b37 100644 --- a/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/StoreSVarAi.java @@ -32,7 +32,7 @@ public class StoreSVarAi extends SpellAbilityAi { // 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; if (xPay == 0) { return false; } - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } final String logic = sa.getParam("AILogic"); 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 54da422ee87..5bd753c2d4d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAi.java @@ -3,18 +3,11 @@ package forge.ai.ability; import forge.ai.*; import forge.game.ability.AbilityUtils; import forge.game.card.Card; -import forge.game.card.CounterEnumType; -import forge.game.card.CounterType; import forge.game.cost.Cost; -import forge.game.cost.CostPart; -import forge.game.cost.CostRemoveCounter; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; - -import java.util.List; public class TapAi extends TapAiBase { @Override @@ -48,7 +41,6 @@ public class TapAi extends TapAiBase { return false; } - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); final Cost abCost = sa.getPayCosts(); if (abCost != null) { @@ -57,32 +49,27 @@ public class TapAi extends TapAiBase { } } - if (tgt == null) { - final List defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); - + if (!sa.usesTargeting()) { boolean bFlag = false; - for (final Card c : defined) { + for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) { bFlag |= c.isUntapped(); } return bFlag; } else { - if ("TapForXCounters".equals(sa.getParam("AILogic"))) { - // e.g. Waxmane Baku - CounterType ctrType = CounterType.get(CounterEnumType.KI); - for (CostPart part : sa.getPayCosts().getCostParts()) { - if (part instanceof CostRemoveCounter) { - ctrType = ((CostRemoveCounter)part).counter; - break; - } - } + // 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); + sa.setSVar("PayX", Integer.toString(xPay)); - int numTargetable = Math.min(sa.getHostCard().getCounters(ctrType), ai.getOpponents().getCreaturesInPlay().size()); - sa.setSVar("ChosenX", String.valueOf(numTargetable)); + // TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore? + sa.setXManaCostPaid(xPay); + // TODO since change of PayX. the shouldCastLessThanMax logic might be faulty } sa.resetTargets(); - return tapPrefTargeting(ai, source, tgt, sa, false); + return tapPrefTargeting(ai, source, sa, false); } } diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java index 98734bc4f2f..e198d6f26ee 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java @@ -18,12 +18,11 @@ import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.game.zone.ZoneType; import java.util.List; -public abstract class TapAiBase extends SpellAbilityAi { +public abstract class TapAiBase extends SpellAbilityAi { /** *

@@ -41,21 +40,18 @@ public abstract class TapAiBase extends SpellAbilityAi { */ private boolean tapTargetList(final Player ai, final SpellAbility sa, final CardCollection tapList, final boolean mandatory) { final Card source = sa.getHostCard(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); - for (final Card c : sa.getTargets().getTargetCards()) { - tapList.remove(c); - } + tapList.removeAll(sa.getTargets().getTargetCards()); if (tapList.isEmpty()) { return false; } - while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) { + while (sa.canAddMoreTarget()) { Card choice = null; - if (tapList.size() == 0) { - if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) { + if (tapList.isEmpty()) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (!mandatory) { sa.resetTargets(); } @@ -76,7 +72,7 @@ public abstract class TapAiBase extends SpellAbilityAi { } if (choice == null) { // can't find anything left - if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (!mandatory) { sa.resetTargets(); } @@ -111,11 +107,10 @@ public abstract class TapAiBase extends SpellAbilityAi { * a boolean. * @return a boolean. */ - protected boolean tapPrefTargeting(final Player ai, final Card source, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory) { + protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) { final Player opp = ai.getWeakestOpponent(); final Game game = ai.getGame(); CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); - tapList = CardLists.getValidCards(tapList, tgt.getValidTgts(), source.getController(), source, sa); tapList = CardLists.getTargetableCards(tapList, sa); tapList = CardLists.filter(tapList, Presets.UNTAPPED); tapList = CardLists.filter(tapList, new Predicate() { @@ -136,10 +131,9 @@ public abstract class TapAiBase extends SpellAbilityAi { //use broader approach when the cost is a positive thing if (tapList.isEmpty() && ComputerUtil.activateForCost(sa, ai)) { - tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); - tapList = CardLists.getValidCards(tapList, tgt.getValidTgts(), source.getController(), source, sa); + tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); tapList = CardLists.getTargetableCards(tapList, sa); - tapList = CardLists.filter(tapList, new Predicate() { + tapList = CardLists.filter(tapList, new Predicate() { @Override public boolean apply(final Card c) { if (c.isCreature()) { @@ -162,15 +156,15 @@ public abstract class TapAiBase extends SpellAbilityAi { tapList.removeAll(toExclude); if (tapList.isEmpty()) { - return false; + return false; } boolean goodTargets = false; - while (sa.getTargets().size() < tgt.getMaxTargets(source, sa)) { + while (sa.canAddMoreTarget()) { Card choice = null; if (tapList.isEmpty()) { - if (sa.getTargets().size() < tgt.getMinTargets(source, sa) || sa.getTargets().size() == 0) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (!mandatory) { sa.resetTargets(); } @@ -186,8 +180,8 @@ public abstract class TapAiBase extends SpellAbilityAi { PhaseHandler phase = game.getPhaseHandler(); Card primeTarget = ComputerUtil.getKilledByTargeting(sa, tapList); if (primeTarget != null) { - choice = primeTarget; - goodTargets = true; + choice = primeTarget; + goodTargets = true; } else if (phase.isPlayerTurn(ai) && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) { // Tap creatures possible blockers before combat during AI's turn. List attackers; @@ -231,7 +225,7 @@ public abstract class TapAiBase extends SpellAbilityAi { } if (choice == null) { // can't find anything left - if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { if (!mandatory) { sa.resetTargets(); } @@ -265,19 +259,17 @@ public abstract class TapAiBase extends SpellAbilityAi { */ protected boolean tapUnpreferredTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) { final Card source = sa.getHostCard(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Game game = ai.getGame(); - CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), source.getController(), source, sa); - list = CardLists.getTargetableCards(list, sa); - + CardCollection list = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa); + // try to tap anything controlled by the computer CardCollection tapList = CardLists.filterControlledBy(list, ai.getOpponents()); if (tapTargetList(ai, sa, tapList, mandatory)) { return true; } - if (sa.getTargets().size() >= tgt.getMinTargets(sa.getHostCard(), sa)) { + if (sa.isMinTargetChosen()) { return true; } @@ -296,7 +288,7 @@ public abstract class TapAiBase extends SpellAbilityAi { return true; } - if (sa.getTargets().size() >= tgt.getMinTargets(sa.getHostCard(), sa)) { + if (sa.isMinTargetChosen()) { return true; } @@ -308,11 +300,9 @@ public abstract class TapAiBase extends SpellAbilityAi { @Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); - if (tgt == null) { + if (!sa.usesTargeting()) { if (mandatory) { return true; } @@ -322,7 +312,7 @@ public abstract class TapAiBase extends SpellAbilityAi { return true; } else { sa.resetTargets(); - if (tapPrefTargeting(ai, source, tgt, sa, mandatory)) { + if (tapPrefTargeting(ai, source, sa, mandatory)) { return true; } else if (mandatory) { // not enough preferred targets, but mandatory so keep going: @@ -335,17 +325,14 @@ public abstract class TapAiBase extends SpellAbilityAi { @Override public boolean chkAIDrawback(SpellAbility sa, Player ai) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); boolean randomReturn = true; - if (tgt == null) { - // either self or defined, either way should be fine - } else { + if (sa.usesTargeting()) { // target section, maybe pull this out? sa.resetTargets(); - if (!tapPrefTargeting(ai, source, tgt, sa, false)) { + if (!tapPrefTargeting(ai, source, sa, false)) { return false; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/TapOrUntapAi.java b/forge-ai/src/main/java/forge/ai/ability/TapOrUntapAi.java index b2655c16ff5..949bb217141 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapOrUntapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapOrUntapAi.java @@ -4,11 +4,8 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -import forge.game.spellability.TargetRestrictions; import forge.util.MyRandom; -import java.util.List; - public class TapOrUntapAi extends TapAiBase { /* (non-Javadoc) @@ -16,19 +13,17 @@ public class TapOrUntapAi extends TapAiBase { */ @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); - if (tgt == null) { + if (!sa.usesTargeting()) { // assume we are looking to tap human's stuff // TODO - check for things with untap abilities, and don't tap // those. - final List defined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); boolean bFlag = false; - for (final Card c : defined) { + for (final Card c : AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa)) { bFlag |= c.isUntapped(); } @@ -37,7 +32,7 @@ public class TapOrUntapAi extends TapAiBase { } } else { sa.resetTargets(); - if (!tapPrefTargeting(ai, source, tgt, sa, false)) { + if (!tapPrefTargeting(ai, source, sa, false)) { return false; } } 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 f6a62f567ef..7a6eb371c7c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java @@ -85,10 +85,10 @@ public class TokenAi extends SpellAbilityAi { if (source.getSVar("X").equals("Count$Converge")) { x = ComputerUtilMana.getConvergeCount(sa, ai); } - if (source.getSVar("X").equals("Count$xPaid")) { + if (sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. x = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(x)); + sa.setSVar("PayX", Integer.toString(x)); } if (x <= 0) { return false; // 0 tokens or 0 toughness token(s) @@ -261,10 +261,10 @@ public class TokenAi extends SpellAbilityAi { if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) { int x = AbilityUtils.calculateAmount(source, tokenAmount, sa); - if (source.getSVar("X").equals("Count$xPaid")) { + if (sa.getSVar("X").equals("Count$xPaid")) { // Set PayX here to maximum value. x = ComputerUtilMana.determineLeftoverMana(sa, ai); - source.setSVar("PayX", Integer.toString(x)); + sa.setSVar("PayX", Integer.toString(x)); } if (x <= 0) { return false; 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 4c877974ff2..a6dd8205ec6 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java @@ -46,7 +46,7 @@ public class UnattachAllAi extends SpellAbilityAi { return false; } - source.setSVar("PayX", Integer.toString(xPay)); + sa.setSVar("PayX", Integer.toString(xPay)); } if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) 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 5145906e259..ecb45dd2ec8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java @@ -51,27 +51,24 @@ public class UntapAi extends SpellAbilityAi { @Override protected boolean checkApiLogic(Player ai, SpellAbility sa) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); final Card source = sa.getHostCard(); if (ComputerUtil.preventRunAwayActivations(sa)) { return false; } - if (tgt == null) { + if (!sa.usesTargeting()) { final List pDefined = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai; } else { - return untapPrefTargeting(ai, tgt, sa, false); + return untapPrefTargeting(ai, sa, false); } } @Override protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { - final TargetRestrictions tgt = sa.getTargetRestrictions(); - - if (tgt == null) { + if (!sa.usesTargeting()) { if (mandatory) { return true; } @@ -80,7 +77,7 @@ public class UntapAi extends SpellAbilityAi { final List pDefined = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa); return pDefined == null || !pDefined.get(0).isUntapped() || pDefined.get(0).getController() != ai; } else { - if (untapPrefTargeting(ai, tgt, sa, mandatory)) { + if (untapPrefTargeting(ai, sa, mandatory)) { return true; } else if (mandatory) { // not enough preferred targets, but mandatory so keep going: @@ -100,7 +97,7 @@ public class UntapAi extends SpellAbilityAi { if (tgt == null) { // who cares if its already untapped, it's only a subability? } else { - if (!untapPrefTargeting(ai, tgt, sa, false)) { + if (!untapPrefTargeting(ai, sa, false)) { return false; } } @@ -113,15 +110,13 @@ public class UntapAi extends SpellAbilityAi { * untapPrefTargeting. *

* - * @param tgt - * a {@link forge.game.spellability.TargetRestrictions} object. * @param sa * a {@link forge.game.spellability.SpellAbility} object. * @param mandatory * a boolean. * @return a boolean. */ - private static boolean untapPrefTargeting(final Player ai, final TargetRestrictions tgt, final SpellAbility sa, final boolean mandatory) { + private static boolean untapPrefTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) { final Card source = sa.getHostCard(); Player targetController = ai; @@ -131,7 +126,6 @@ public class UntapAi extends SpellAbilityAi { } CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa); - list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa); if (list.isEmpty()) { return false; @@ -175,7 +169,7 @@ public class UntapAi extends SpellAbilityAi { untapList.removeAll(toExclude); sa.resetTargets(); - while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) { + while (sa.canAddMoreTarget()) { Card choice = null; if (untapList.isEmpty()) { @@ -183,7 +177,7 @@ public class UntapAi extends SpellAbilityAi { if (sa.getSubAbility() != null && sa.getSubAbility().getApi() == ApiType.Animate && !list.isEmpty() && ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) { choice = ComputerUtilCard.getWorstPermanentAI(list, false, false, false, false); - } else if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) { + } else if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { sa.resetTargets(); return false; } else { @@ -204,7 +198,7 @@ public class UntapAi extends SpellAbilityAi { } if (choice == null) { // can't find anything left - if (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa) || sa.getTargets().size() == 0) { + if (!sa.isMinTargetChosen() || sa.isZeroTargets()) { sa.resetTargets(); return false; } else { diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index 06a3d41adee..49409689a32 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -126,7 +126,7 @@ public class ForgeScript { } else if (property.equals("nonManaAbility")) { return !sa.isManaAbility(); } else if (property.equals("withoutXCost")) { - return !sa.isXCost(); + return !sa.costHasManaX(); } else if (property.equals("Buyback")) { return sa.isBuyBackAbility(); } else if (property.equals("Cycling")) { diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 853a1a35758..c41f9023d5c 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -227,12 +227,6 @@ public class GameAction { // ensure that any leftover keyword/type changes are cleared in the state view copied.updateStateForView(); - // Clean up temporary variables such as Sunburst value or announced PayX value - if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) { - copied.clearTemporaryVars(); - } - - if (!suppress) { if (zoneFrom == null) { copied.getOwner().addInboundToken(copied); diff --git a/forge-game/src/main/java/forge/game/GameEntityCounterTable.java b/forge-game/src/main/java/forge/game/GameEntityCounterTable.java index d8c7b9b3403..6e8c7051552 100644 --- a/forge-game/src/main/java/forge/game/GameEntityCounterTable.java +++ b/forge-game/src/main/java/forge/game/GameEntityCounterTable.java @@ -31,15 +31,43 @@ public class GameEntityCounterTable extends ForwardingTable filterToRemove(GameEntity ge) { + Map result = Maps.newHashMap(); + if (!containsRow(ge)) { + result.putAll(ge.getCounters()); + return result; + } + Map alreadyRemoved = row(ge); + for (Map.Entry e : ge.getCounters().entrySet()) { + Integer rest = e.getValue() - (alreadyRemoved.containsKey(e.getKey()) ? alreadyRemoved.get(e.getKey()) : 0); + if (rest > 0) { + result.put(e.getKey(), rest); + } + } + return result; } public Map filterTable(CounterType type, String valid, Card host, SpellAbility sa) { Map result = Maps.newHashMap(); for (Map.Entry e : column(type).entrySet()) { - if (e.getKey().isValid(valid, host.getController(), host, sa)) { + if (e.getValue() > 0 && e.getKey().isValid(valid, host.getController(), host, sa)) { result.put(e.getKey(), e.getValue()); } } 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 a5180904bfb..1c88e8cb5f7 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -6410,17 +6410,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return changed; } - public final void clearTemporaryVars() { - // Add cleanup for all variables that are set temporarily but that need - // to be restored to their original value if a card changes zones - - removeSVar("PayX"); // Temporary AI X announcement variable - removeSVar("IsCastFromPlayEffect"); // Temporary SVar indicating that the spell is cast indirectly via AF Play - setXManaCostPaidByColor(null); - setKickerMagnitude(0); - setPseudoMultiKickerMagnitude(0); - } - public final int getFinalChapterNr() { int n = 0; for (final Trigger t : getTriggers()) { diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 255fdd911dd..5f8630bb9f2 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -51,7 +51,6 @@ import forge.game.spellability.*; import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; -import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.Aggregates; import forge.util.Expressions; @@ -227,50 +226,6 @@ public class CardFactoryUtil { return AbilityFactory.getAbility(ab, sourceCard); } - /** - *

- * isTargetStillValid. - *

- * - * @param ability - * a {@link forge.game.spellability.SpellAbility} object. - * @param target - * a {@link forge.game.card.Card} object. - * @return a boolean. - */ - public static boolean isTargetStillValid(final SpellAbility ability, final Card target) { - Zone zone = target.getGame().getZoneOf(target); - if (zone == null) { - return false; // for tokens that disappeared - } - - final Card source = ability.getHostCard(); - final TargetRestrictions tgt = ability.getTargetRestrictions(); - if (tgt != null) { - // Reconfirm the Validity of a TgtValid, or if the Creature is still - // a Creature - if (tgt.doesTarget() - && !target.isValid(tgt.getValidTgts(), ability.getActivatingPlayer(), ability.getHostCard(), ability)) { - return false; - } - - // Check if the target is in the zone it needs to be in to be targeted - if (!tgt.getZone().contains(zone.getZoneType())) { - return false; - } - } - else { - // If an Aura's target is removed before it resolves, the Aura - // fizzles - if (source.isAura() && !target.isInZone(ZoneType.Battlefield)) { - return false; - } - } - - // Make sure it's still targetable as well - return ability.canTarget(target); - } - // does "target" have protection from "card"? /** *

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 13f747c7fd9..9d89c04eba1 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1360,7 +1360,7 @@ public class CardProperty { } } else if (property.startsWith("hasXCost")) { SpellAbility sa1 = card.getFirstSpellAbility(); - if (sa1 != null && !sa1.isXCost()) { + if (sa1 != null && !sa1.costHasManaX()) { return false; } } else if (property.startsWith("suspended")) { diff --git a/forge-game/src/main/java/forge/game/card/CounterType.java b/forge-game/src/main/java/forge/game/card/CounterType.java index 0cf41244e4f..d64222b1691 100644 --- a/forge-game/src/main/java/forge/game/card/CounterType.java +++ b/forge-game/src/main/java/forge/game/card/CounterType.java @@ -45,6 +45,9 @@ public class CounterType implements Comparable, Serializable { } public static CounterType getType(String name) { + if ("Any".equalsIgnoreCase(name)) { + return null; + } try { return get(CounterEnumType.getType(name)); } catch (final IllegalArgumentException ex) { 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 1c175b57428..bda38820815 100644 --- a/forge-game/src/main/java/forge/game/cost/Cost.java +++ b/forge-game/src/main/java/forge/game/cost/Cost.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Lists; @@ -42,7 +43,7 @@ import forge.util.TextUtil; *

* Cost class. *

- * + * * @author Forge * @version $Id$ */ @@ -105,7 +106,7 @@ public class Cost implements Serializable { /** * Gets the cost parts. - * + * * @return the cost parts */ public final List getCostParts() { @@ -127,11 +128,11 @@ public class Cost implements Serializable { } }); } - + /** * Get the cost parts, always including a mana cost part (which may be * zero). - * + * * @return the cost parts, possibly with an extra zero mana {@link * CostPartMana}. */ @@ -149,7 +150,7 @@ public class Cost implements Serializable { *

* isOnlyManaCost. *

- * + * * @return a boolean. */ public final boolean isOnlyManaCost() { @@ -167,7 +168,7 @@ public class Cost implements Serializable { *

* getTotalMana. *

- * + * * @return a {@link java.lang.String} object. */ public final ManaCost getTotalMana() { @@ -179,17 +180,17 @@ public class Cost implements Serializable { *

* isMandatory *

- * + * * @return boolean */ public final boolean isMandatory() { return this.isMandatory; } - + public final boolean isAbility() { return this.isAbility; } - + private Cost() { } @@ -400,9 +401,9 @@ public class Cost implements Serializable { } if (parse.startsWith("RemoveAnyCounter<")) { - final String[] splitStr = abCostParse(parse, 3); - final String description = splitStr.length > 2 ? splitStr[2] : null; - return new CostRemoveAnyCounter(splitStr[0], splitStr[1], description); + final String[] splitStr = abCostParse(parse, 4); + final String description = splitStr.length > 3 ? splitStr[3] : null; + return new CostRemoveAnyCounter(splitStr[0], CounterType.getType(splitStr[1]), splitStr[2], description); } if (parse.startsWith("Exile<")) { @@ -515,7 +516,7 @@ public class Cost implements Serializable { *

* abCostParse. *

- * + * * @param parse * a {@link java.lang.String} object. * @param numParse @@ -539,7 +540,7 @@ public class Cost implements Serializable { toRet.cacheTapCost(); return toRet; } - + public final Cost copyWithNoMana() { Cost toRet = new Cost(0); toRet.isAbility = this.isAbility; @@ -580,7 +581,7 @@ public class Cost implements Serializable { *

* refundPaidCost. *

- * + * * @param source * a {@link forge.game.card.Card} object. */ @@ -595,7 +596,7 @@ public class Cost implements Serializable { *

* isUndoable. *

- * + * * @return a boolean. */ public final boolean isUndoable() { @@ -612,7 +613,7 @@ public class Cost implements Serializable { *

* isReusuableResource. *

- * + * * @return a boolean. */ public final boolean isReusuableResource() { @@ -629,7 +630,7 @@ public class Cost implements Serializable { *

* isRenewableResource. *

- * + * * @return a boolean. */ public final boolean isRenewableResource() { @@ -646,7 +647,7 @@ public class Cost implements Serializable { *

* toString. *

- * + * * @return a {@link java.lang.String} object. */ @Override @@ -665,7 +666,7 @@ public class Cost implements Serializable { *

* toStringAlt. *

- * + * * @return a {@link java.lang.String} object. */ public final String toStringAlt() { @@ -676,7 +677,7 @@ public class Cost implements Serializable { *

* toSimpleString. *

- * + * * @return a {@link java.lang.String} object. */ public final String toSimpleString() { @@ -696,7 +697,7 @@ public class Cost implements Serializable { *

* spellToString. *

- * + * * @param bFlag * a boolean. * @return a {@link java.lang.String} object. @@ -747,7 +748,7 @@ public class Cost implements Serializable { *

* abilityToString. *

- * + * * @return a {@link java.lang.String} object. */ private String abilityToString() { @@ -788,7 +789,7 @@ public class Cost implements Serializable { /** * Convert amount type to words. - * + * * @param i * the i * @param amount @@ -809,7 +810,7 @@ public class Cost implements Serializable { *

* convertIntAndTypeToWords. *

- * + * * @param i * a int. * @param type @@ -848,7 +849,7 @@ public class Cost implements Serializable { /** * Convert amount type to words. - * + * * @param amount * the amount * @param type @@ -887,12 +888,12 @@ public class Cost implements Serializable { } else { costParts.add(0, new CostPartMana(oldManaCost.toManaCost(), r)); } - } else if (part instanceof CostDiscard || part instanceof CostTapType || + } else if (part instanceof CostDiscard || part instanceof CostTapType || part instanceof CostAddMana || part instanceof CostPayLife) { boolean alreadyAdded = false; for (final CostPart other : costParts) { if (other.getClass().equals(part.getClass()) && - part.getType().equals(other.getType()) && + part.getType().equals(other.getType()) && StringUtils.isNumeric(part.getAmount()) && StringUtils.isNumeric(other.getAmount())) { final String amount = String.valueOf(Integer.parseInt(part.getAmount()) + Integer.parseInt(other.getAmount())); @@ -959,5 +960,20 @@ public class Cost implements Serializable { return xCost; } + public Integer getMaxForNonManaX(final SpellAbility ability, final Player payer) { + Integer val = null; + for (CostPart p : getCostParts()) { + if (!p.getAmount().equals("X")) { + continue; + } + + val = ObjectUtils.min(val, p.getMaxAmountX(ability, payer)); + } + // extra 0 check + if (val != null && val <= 0 && hasManaCost() && !getCostMana().canXbe0()) { + val = null; + } + return val; + } public static final Cost Zero = new Cost(0); } 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 cea0bfb9ee8..41434860833 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPart.java +++ b/forge-game/src/main/java/forge/game/cost/CostPart.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -49,7 +49,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Instantiates a new cost part. - * + * * @param amount * the amount * @param type @@ -67,16 +67,19 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Gets the amount. - * + * * @return the amount */ public final String getAmount() { return this.amount; } + public Integer getMaxAmountX(final SpellAbility ability, final Player payer) { + return null; + } /** * Gets the type. - * + * * @return the type */ public final String getType() { @@ -85,7 +88,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Gets the this. - * + * * @return the this */ public final boolean payCostFromSource() { @@ -94,7 +97,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Gets the type description. - * + * * @return the type description */ public final String getTypeDescription() { @@ -108,16 +111,16 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Checks if is reusable. - * + * * @return true, if is reusable */ public boolean isReusable() { return false; } - + /** * Checks if is renewable. - * + * * @return true, if is renewable */ public boolean isRenewable() { @@ -126,7 +129,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Checks if is undoable. - * + * * @return true, if is undoable */ public boolean isUndoable() { @@ -135,19 +138,19 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Convert amount. - * + * * @return the integer */ public final Integer convertAmount() { - return StringUtils.isNumeric(amount) ? Integer.parseInt(amount) : null; + return StringUtils.isNumeric(amount) ? Integer.parseInt(amount) : null; } /** * Can pay. - * + * * @param ability * the ability - * @param payer + * @param payer * @return true, if successful */ public abstract boolean canPay(SpellAbility ability, Player payer); @@ -156,7 +159,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /* * (non-Javadoc) - * + * * @see java.lang.Object#toString() */ @Override @@ -164,7 +167,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Refund. Overridden in classes which know how to refund. - * + * * @param source * the source */ @@ -173,7 +176,7 @@ public abstract class CostPart implements Comparable, Cloneable, Seria /** * Sets the amount. - * + * * @param amountIn * the amount to set */ 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 5402e5e6749..d683ae8b618 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPayEnergy.java +++ b/forge-game/src/main/java/forge/game/cost/CostPayEnergy.java @@ -47,6 +47,10 @@ public class CostPayEnergy extends CostPart { @Override public int paymentOrder() { return 7; } + public Integer getMaxAmountX(final SpellAbility ability, final Player payer) { + return payer.getCounters(CounterEnumType.ENERGY); + } + /* * (non-Javadoc) * 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 063a44ce224..7751acde500 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostRemoveAnyCounter.java @@ -6,30 +6,30 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package forge.game.cost; +import forge.game.GameEntity; import forge.game.ability.AbilityUtils; import forge.game.card.*; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; -import forge.util.TextUtil; -import java.util.Map; +import com.google.common.collect.Table; /** * The Class CostRemoveAnyCounter. */ -public class CostRemoveAnyCounter extends CostPartWithList { +public class CostRemoveAnyCounter extends CostPart { /** * Serializables need a version ID. */ @@ -37,13 +37,8 @@ public class CostRemoveAnyCounter extends CostPartWithList { // RemoveAnyCounter // Power Conduit and Chisei, Heart of Oceans // Both cards have "Remove a counter from a permanent you control" - private CounterType counterType; - /** - * @param counterType the counterType to set - */ - public void setCounterType(CounterType counterType) { - this.counterType = counterType; - } + + public final CounterType counter; /** * Instantiates a new cost CostRemoveAnyCounter. @@ -51,33 +46,34 @@ public class CostRemoveAnyCounter extends CostPartWithList { * @param amount * the amount */ - public CostRemoveAnyCounter(final String amount, final String type, final String description) { + public CostRemoveAnyCounter(final String amount, final CounterType counter, final String type, final String description) { super(amount, type, description); + this.counter = counter; } @Override public int paymentOrder() { return 8; } - /* (non-Javadoc) - * @see forge.card.cost.CostPartWithList#getHashForList() - */ @Override - public String getHashForLKIList() { - return "CounterRemove"; - } - @Override - public String getHashForCardList() { - return "CounterRemoveCards"; + public Integer getMaxAmountX(final SpellAbility ability, final Player payer) { + + final Card source = ability.getHostCard(); + + CardCollectionView validCards = CardLists.getValidCards(payer.getCardsIn(ZoneType.Battlefield), this.getType().split(";"), payer, source, ability); + int allCounters = 0; + for (Card c : validCards) { + if (this.counter != null) { + allCounters += c.getCounters(this.counter); + } else { + for (Integer value : c.getCounters().values()) { + allCounters += value; + } + } + } + return allCounters; } - /** - * Gets the counter. - * - * @return the counter - */ - public CounterType getCounter() { - return this.counterType; - } + /* * (non-Javadoc) @@ -88,27 +84,7 @@ public class CostRemoveAnyCounter extends CostPartWithList { */ @Override public final boolean canPay(final SpellAbility ability, final Player payer) { - final Card source = ability.getHostCard(); - CardCollectionView validCards = payer.getCardsIn(ZoneType.Battlefield); - validCards = CardLists.getValidCards(validCards, this.getType().split(";"), payer, source, ability); - validCards = CardLists.filter(validCards, CardPredicates.hasCounters()); - if (validCards.isEmpty()) { - return false; - } - Integer i = this.convertAmount(); - - if (i == null) { - i = AbilityUtils.calculateAmount(source, this.getAmount(), ability); - } - int allCounters = 0; - for (Card c : validCards) { - final Map tgtCounters = c.getCounters(); - for (Integer value : tgtCounters.values()) { - allCounters += value; - } - } - - return i <= allCounters; + return AbilityUtils.calculateAmount(ability.getHostCard(), this.getAmount(), ability) <= getMaxAmountX(ability, payer); } /* @@ -120,39 +96,29 @@ public class CostRemoveAnyCounter extends CostPartWithList { public final String toString() { final StringBuilder sb = new StringBuilder(); + String counters = this.counter == null ? "counter" : this.counter.getName() + " counter"; + sb.append("Remove "); - sb.append(Cost.convertIntAndTypeToWords(this.convertAmount(), "counter")); + sb.append(Cost.convertAmountTypeToWords(this.convertAmount(), this.getAmount(), counters)); final String desc = this.getTypeDescription() == null ? this.getType() : this.getTypeDescription(); sb.append(" from ").append(desc); return sb.toString(); } - @Override - public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) { - final String amount = this.getAmount(); - final Card source = ability.getHostCard(); - Integer c = this.convertAmount(); - if (c == null) { - c = AbilityUtils.calculateAmount(source, amount, ability); - } - if (decision.cards.isEmpty()) { - System.err.println(TextUtil.concatWithSpace("Warning: payment decision array was empty when paying CostRemoveAnyCounter for" , ability.getDescription(), "from", ability.getHostCard().toString())); - return false; - } - Card valid = decision.cards.get(0); - counterType = decision.ct; - for (int i = 0; i < c; i++) { - executePayment(ability, valid); - } - source.setSVar("CostCountersRemoved", Integer.toString(c)); - return true; - } @Override - protected Card doPayment(SpellAbility ability, Card targetCard){ - targetCard.subtractCounter(this.getCounter(), 1); - return targetCard; + public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) { + final Card source = ability.getHostCard(); + + int removed = 0; + for (Table.Cell cell : decision.counterTable.cellSet()) { + removed += cell.getValue(); + cell.getRowKey().subtractCounter(cell.getColumnKey(), cell.getValue()); + } + + source.setSVar("CostCountersRemoved", Integer.toString(removed)); + return true; } public T accept(ICostVisitor visitor) { 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 d03d99cd45b..4788fdec31b 100644 --- a/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostRemoveCounter.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -19,6 +19,7 @@ package forge.game.cost; import com.google.common.collect.Lists; +import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardLists; import forge.game.card.CounterEnumType; @@ -32,7 +33,7 @@ import java.util.List; /** * The Class CostRemoveCounter. */ -public class CostRemoveCounter extends CostPartWithList { +public class CostRemoveCounter extends CostPart { // SubCounter // Here are the cards that have RemoveCounter @@ -47,11 +48,10 @@ public class CostRemoveCounter extends CostPartWithList { private static final long serialVersionUID = 1L; public final CounterType counter; public final ZoneType zone; - private int cntRemoved; /** * Instantiates a new cost remove counter. - * + * * @param amount * the amount * @param counter @@ -73,11 +73,32 @@ public class CostRemoveCounter extends CostPartWithList { public int paymentOrder() { return 8; } @Override - public boolean isUndoable() { return true; } + public Integer getMaxAmountX(final SpellAbility ability, final Player payer) { + final CounterType cntrs = this.counter; + final Card source = ability.getHostCard(); + final String type = this.getType(); + if (this.payCostFromSource()) { + return source.getCounters(cntrs); + } else { + List typeList; + if (type.equals("OriginalHost")) { + typeList = Lists.newArrayList(ability.getOriginalHost()); + } else { + typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability); + } + + // Single Target + int maxcount = 0; + for (Card c : typeList) { + maxcount = Math.max(maxcount, c.getCounters(cntrs)); + } + return maxcount; + } + } /* * (non-Javadoc) - * + * * @see forge.card.cost.CostPart#toString() */ @Override @@ -108,20 +129,7 @@ public class CostRemoveCounter extends CostPartWithList { /* * (non-Javadoc) - * - * @see forge.card.cost.CostPart#refund(forge.Card) - */ - @Override - public final void refund(final Card source) { - int refund = this.getCardList().size() == 1 ? this.cntRemoved : 1; // is wrong for Ooze Flux and Novijen Sages - for (final Card c : this.getCardList()) { - c.addCounter(this.counter, refund, source.getController(), false, false, null); - } - } - - /* - * (non-Javadoc) - * + * * @see * forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility, * forge.Card, forge.Player, forge.card.cost.Cost) @@ -144,21 +152,10 @@ public class CostRemoveCounter extends CostPartWithList { typeList = CardLists.getValidCards(payer.getCardsIn(this.zone), type.split(";"), payer, source, ability); } if (amount != null) { - // TODO find better way than TypeDescription - if (this.getTypeDescription().equals("among creatures you control")) { - // remove X counters from among creatures you control - int totalCounters = 0; - for (Card c : typeList) { - totalCounters += c.getCounters(cntrs); - } - return totalCounters >= amount; - - } else { - // (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; @@ -171,32 +168,19 @@ public class CostRemoveCounter extends CostPartWithList { @Override public boolean payAsDecided(Player ai, PaymentDecision decision, SpellAbility ability) { Card source = ability.getHostCard(); - cntRemoved = decision.c; - for (final Card card : decision.cards) { - executePayment(ability, card); + + int removed = 0; + int toRemove = AbilityUtils.calculateAmount(source, getAmount(), ability); + // for this cost, the list should be only one + for (Card c : decision.cards) { + removed += toRemove; + c.subtractCounter(counter, toRemove); } - source.setSVar("CostCountersRemoved", Integer.toString(cntRemoved)); + + source.setSVar("CostCountersRemoved", Integer.toString(removed)); return true; } - @Override - protected Card doPayment(SpellAbility ability, Card targetCard){ - targetCard.subtractCounter(this.counter, cntRemoved); - return targetCard; - } - - /* (non-Javadoc) - * @see forge.card.cost.CostPartWithList#getHashForList() - */ - @Override - public String getHashForLKIList() { - return "CounterRemove"; - } - @Override - public String getHashForCardList() { - return "CounterRemoveCards"; - } - public T accept(ICostVisitor visitor) { return visitor.visit(this); } diff --git a/forge-game/src/main/java/forge/game/cost/PaymentDecision.java b/forge-game/src/main/java/forge/game/cost/PaymentDecision.java index 1cbfd22c138..e10ff87eb11 100644 --- a/forge-game/src/main/java/forge/game/cost/PaymentDecision.java +++ b/forge-game/src/main/java/forge/game/cost/PaymentDecision.java @@ -1,5 +1,6 @@ package forge.game.cost; +import forge.game.GameEntityCounterTable; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CounterType; @@ -10,72 +11,74 @@ import forge.util.TextUtil; import java.util.List; - public class PaymentDecision { public int c = 0; public String type; - public CounterType ct; public final CardCollection cards = new CardCollection(); public final List mana; public final List players; public final List sp; + // used for CostRemoveAnyCounter + public final GameEntityCounterTable counterTable; + public PaymentDecision(int cnt) { - this(null, null, null, null); + this(null, null, null, null, null); c = cnt; } private PaymentDecision(Iterable chosen, List manaProduced, List players, - List sp) { + List sp, GameEntityCounterTable counterTable) { if (chosen != null) { cards.addAll(chosen); } mana = manaProduced; this.players = players; this.sp = sp; + this.counterTable = counterTable; } - + private PaymentDecision(Card chosen) { - this(null, null, null, null); + this(null, null, null, null, null); cards.add(chosen); } - + public PaymentDecision(String choice) { - this(null, null, null, null); + this(null, null, null, null, null); type = choice; } public static PaymentDecision card(Card chosen) { return new PaymentDecision(chosen); } - + public static PaymentDecision card(Card chosen, int n) { PaymentDecision res = new PaymentDecision(chosen); res.c = n; return res; } - - + + public static PaymentDecision number(int c) { return new PaymentDecision(c); } public static PaymentDecision card(Iterable chosen) { - return new PaymentDecision(chosen, null, null, null); + return new PaymentDecision(chosen, null, null, null, null); } - + public static PaymentDecision card(Iterable chosen, int n) { - PaymentDecision res = new PaymentDecision(chosen, null, null, null); + PaymentDecision res = new PaymentDecision(chosen, null, null, null, null); res.c = n; return res; } - + public static PaymentDecision mana(List manas) { - return new PaymentDecision(null, manas, null, null); + return new PaymentDecision(null, manas, null, null, null); } - - + + /* (non-Javadoc) * @see java.lang.Object#toString() */ @@ -90,16 +93,14 @@ public class PaymentDecision { public static PaymentDecision players(List players) { // TODO Auto-generated method stub - return new PaymentDecision(null, null, players, null); - } - - public static PaymentDecision spellabilities(List sp) { - return new PaymentDecision(null, null, null, sp); + return new PaymentDecision(null, null, players, null, null); } - public static PaymentDecision card(Card selected, CounterType counterType) { - PaymentDecision res = card(selected); - res.ct = counterType; - return res; + public static PaymentDecision spellabilities(List sp) { + return new PaymentDecision(null, null, null, sp, null); + } + + public static PaymentDecision counters(GameEntityCounterTable counterTable) { + return new PaymentDecision(null, null, null, null, counterTable); } } diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java b/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java index 050c551f62c..42d2e543a1b 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java @@ -64,9 +64,7 @@ public abstract class AbilityActivated extends SpellAbility implements Cloneable */ public AbilityActivated(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) { super(sourceCard, abCost); - if ((tgt != null) && tgt.doesTarget()) { - this.setTargetRestrictions(tgt); - } + this.setTargetRestrictions(tgt); } public boolean isActivatedAbility() { return true; } diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index 0b5ff4626ef..c867926a28b 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -348,7 +348,7 @@ public class AbilityManaPart implements java.io.Serializable { } if (restriction.startsWith("CostContainsX")) { - if (sa.isXCost()) { + if (sa.costHasManaX()) { return true; } continue; diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java b/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java index bd14a4c8834..9c769c5e982 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityStatic.java @@ -47,9 +47,7 @@ public abstract class AbilityStatic extends Ability implements Cloneable { public AbilityStatic(final Card sourceCard, final Cost abCost, final TargetRestrictions tgt) { super(sourceCard, abCost); - if ((tgt != null) && tgt.doesTarget()) { - this.setTargetRestrictions(tgt); - } + this.setTargetRestrictions(tgt); } @Override public boolean canPlay() { diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 227a81902ee..3fcba014cea 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -39,7 +39,6 @@ import forge.game.card.CardPredicates; import forge.game.card.CardZoneTable; import forge.game.cost.Cost; import forge.game.cost.CostPart; -import forge.game.cost.CostPartMana; import forge.game.cost.CostRemoveCounter; import forge.game.keyword.Keyword; import forge.game.mana.Mana; @@ -987,7 +986,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit final TargetRestrictions tr = getTargetRestrictions(); // Restriction related to this ability - if (tr != null) { + if (usesTargeting()) { if (tr.isUniqueTargets() && getUniqueTargets().contains(entity)) return false; @@ -1324,11 +1323,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit originalMapParams.put("Announce", announce + ";" + variable); } - public boolean isXCost() { - CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null; - return cm != null && cm.getAmountOfX() > 0; - } - @Override public boolean canBeTargetedBy(SpellAbility sa) { return sa.canTargetSpellAbility(this); @@ -1406,14 +1400,22 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return getTargetRestrictions().getMinTargets(hostCard, this) == 0 && getTargets().size() == 0; } + public boolean isMinTargetChosen() { + return getTargetRestrictions().isMinTargetsChosen(hostCard, this); + } + public boolean isMaxTargetChosen() { + return getTargetRestrictions().isMaxTargetsChosen(hostCard, this); + } + public boolean isTargetNumberValid() { if (!this.usesTargeting()) { return getTargets().isEmpty(); } - int minTargets = getTargetRestrictions().getMinTargets(hostCard, this); + if (!isMinTargetChosen()) { + return false; + } int maxTargets = getTargetRestrictions().getMaxTargets(hostCard, this); - int numTargets = getTargets().size(); if (maxTargets == 0 && getPayCosts().hasSpecificCostType(CostRemoveCounter.class) && hasSVar(getParam("TargetMax")) @@ -1426,7 +1428,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit maxTargets = Integer.parseInt(getHostCard().getSVar("CostCountersRemoved")); } - return minTargets <= numTargets && maxTargets >= numTargets; + return maxTargets >= getTargets().size(); } /** *

@@ -1667,8 +1669,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit SpellAbility currentAbility = this; final Card source = getHostCard(); do { - final TargetRestrictions tgt = currentAbility.getTargetRestrictions(); - if (tgt != null && tgt.doesTarget()) { + if (currentAbility.usesTargeting()) { currentAbility.clearTargets(); Player targetingPlayer; if (currentAbility.hasParam("TargetingPlayer")) { diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java index 887c845d163..687d58ffdad 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java @@ -24,7 +24,6 @@ import forge.game.GameType; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardCollectionView; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardUtil; import forge.game.phase.PhaseHandler; @@ -271,7 +270,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables { if (this.isAllTargetsLegal()) { for (Card c : sa.getTargets().getTargetCards()) { - if (!CardFactoryUtil.isTargetStillValid(sa, c)) { + if (!sa.canTarget(c)) { return false; } } diff --git a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java index 03ed158bcb6..d917a4058f9 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java @@ -49,7 +49,6 @@ public class TargetRestrictions { // Target Choices (which is specific for the StackInstance) // What this Object is restricted to targeting - private boolean tgtValid = false; private String[] originalValidTgts, validTgts; private String uiPrompt = ""; @@ -93,7 +92,6 @@ public class TargetRestrictions { * a {@link forge.game.spellability.TargetRestrictions} object. */ public TargetRestrictions(final TargetRestrictions target) { - this.tgtValid = true; this.uiPrompt = target.getVTSelection(); this.originalValidTgts = target.getValidTgts(); this.validTgts = this.originalValidTgts.clone(); @@ -129,7 +127,6 @@ public class TargetRestrictions { * a {@link java.lang.String} object. */ public TargetRestrictions(final String prompt, final String[] valid, final String min, final String max) { - this.tgtValid = true; this.uiPrompt = prompt; this.originalValidTgts = valid; this.validTgts = this.originalValidTgts.clone(); @@ -172,17 +169,6 @@ public class TargetRestrictions { this.maxTotalCMC = cmc; } - /** - *

- * doesTarget. - *

- * - * @return a boolean. - */ - public final boolean doesTarget() { - return this.tgtValid; - } - /** *

* getValidTgts. @@ -210,7 +196,7 @@ public class TargetRestrictions { * * @return the min targets */ - private final String getMinTargets() { + public final String getMinTargets() { return this.minTargets; } @@ -219,7 +205,7 @@ public class TargetRestrictions { * * @return the max targets */ - private final String getMaxTargets() { + public final String getMaxTargets() { return this.maxTargets; } @@ -278,8 +264,7 @@ public class TargetRestrictions { * @return a boolean. */ public final boolean isMaxTargetsChosen(final Card c, final SpellAbility sa) { - TargetChoices choice = sa.getTargets(); - return this.getMaxTargets(c, sa) == choice.size(); + return this.getMaxTargets(c, sa) == sa.getTargets().size(); } /** @@ -294,11 +279,11 @@ public class TargetRestrictions { * @return a boolean. */ public final boolean isMinTargetsChosen(final Card c, final SpellAbility sa) { - if (this.getMinTargets(c, sa) == 0) { + int min = getMinTargets(c, sa); + if (min == 0) { return true; } - TargetChoices choice = sa.getTargets(); - return this.getMinTargets(c, sa) <= choice.size(); + return min <= sa.getTargets().size(); } /** 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 ba22a8349e7..ba82b59502f 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -41,7 +41,6 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardCollection; -import forge.game.card.CardFactoryUtil; import forge.game.card.CardUtil; import forge.game.event.EventValueChangeType; import forge.game.event.GameEventCardStatsChanged; @@ -593,7 +592,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable | Defined$ Self | NumAtt$ +Y | References$ X,Y | SpellDescription$ For each counter removed, CARDNAME gets +2/+0 until end of turn. -SVar:X:XChoice -#ChosenX SVar created by Cost payment -SVar:Y:SVar$ChosenX/Times.2 +SVar:X:Count$xPaid +SVar:Y:SVar$X/Times.2 AI:RemoveDeck:Random DeckHints:Type$Spirit|Arcane -SVar:Picture:http://www.wizards.com/global/images/magic/general/blademane_baku.jpg Oracle:Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Blademane Baku.\n{1}, Remove X ki counters from Blademane Baku: For each counter removed, Blademane Baku gets +2/+0 until end of turn. diff --git a/forge-gui/res/cardsfolder/c/chamber_sentry.txt b/forge-gui/res/cardsfolder/c/chamber_sentry.txt index 476a1d5715a..877e5f46cf3 100644 --- a/forge-gui/res/cardsfolder/c/chamber_sentry.txt +++ b/forge-gui/res/cardsfolder/c/chamber_sentry.txt @@ -4,7 +4,7 @@ Types:Artifact Creature Construct PT:0/0 K:etbCounter:P1P1:Y:no Condition:CARDNAME enters the battlefield with a +1/+1 counter on it for each color of mana spent to cast it. SVar:Y:Count$Converge -A:AB$ DealDamage | Announce$ X | Cost$ X T SubCounter | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | References$ X | AILogic$ XCountersDamage | SpellDescription$ CARDNAME deals X damage to any target. +A:AB$ DealDamage | Cost$ X T SubCounter | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | References$ X | SpellDescription$ CARDNAME deals X damage to any target. SVar:X:Count$xPaid A:AB$ ChangeZone | Cost$ W U B R G | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to your hand. SVar:DiscardMe:1 diff --git a/forge-gui/res/cardsfolder/c/chisei_heart_of_oceans.txt b/forge-gui/res/cardsfolder/c/chisei_heart_of_oceans.txt index 3abbb3ec535..5e9cca07dfe 100644 --- a/forge-gui/res/cardsfolder/c/chisei_heart_of_oceans.txt +++ b/forge-gui/res/cardsfolder/c/chisei_heart_of_oceans.txt @@ -4,8 +4,8 @@ Types:Legendary Creature Spirit PT:4/4 K:Flying T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSac | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you remove a counter from a permanent you control. -SVar:TrigSac:DB$ Sacrifice | Defined$ Self | UnlessPayer$ You | UnlessCost$ RemoveAnyCounter<1/Permanent.YouCtrl/a permanent you control> +SVar:TrigSac:DB$ Sacrifice | Defined$ Self | UnlessPayer$ You | UnlessCost$ RemoveAnyCounter<1/Any/Permanent.YouCtrl/a permanent you control> DeckNeeds:Ability$Counters SVar:NeedsToPlay:Creature.YouCtrl+HasCounters -SVar:Picture:http://www.wizards.com/global/images/magic/general/chisei_heart_of_oceans.jpg +SVar:AIRemoveCounterCostPriority:ANY Oracle:Flying\nAt the beginning of your upkeep, sacrifice Chisei, Heart of Oceans unless you remove a counter from a permanent you control. diff --git a/forge-gui/res/cardsfolder/g/garruk_primal_hunter.txt b/forge-gui/res/cardsfolder/g/garruk_primal_hunter.txt index ec658e672f6..c6c8ab5f467 100644 --- a/forge-gui/res/cardsfolder/g/garruk_primal_hunter.txt +++ b/forge-gui/res/cardsfolder/g/garruk_primal_hunter.txt @@ -7,5 +7,4 @@ A:AB$ Draw | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | Defined$ You | N SVar:X:Count$GreatestPower_Creature.YouCtrl A:AB$ Token | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | TokenAmount$ Y | TokenScript$ g_6_6_wurm | TokenOwner$ You | LegacyImage$ g 6 6 wurm m12 | SpellDescription$ Create a 6/6 green Wurm creature for each land you control. SVar:Y:Count$Valid Land.YouCtrl -SVar:Picture:http://www.wizards.com/global/images/magic/general/garruk_primal_hunter.jpg Oracle:[+1]: Create a 3/3 green Beast creature token.\n[-3]: Draw cards equal to the greatest power among creatures you control.\n[-6]: Create a 6/6 green Wurm creature for each land you control. diff --git a/forge-gui/res/cardsfolder/h/huatli_warrior_poet.txt b/forge-gui/res/cardsfolder/h/huatli_warrior_poet.txt index 9357d49fdb3..7977fee52a6 100644 --- a/forge-gui/res/cardsfolder/h/huatli_warrior_poet.txt +++ b/forge-gui/res/cardsfolder/h/huatli_warrior_poet.txt @@ -6,10 +6,9 @@ A:AB$ GainLife | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | LifeAmount$ SVar:GreatestPow:Count$GreatestPower_Creature.YouCtrl A:AB$ Token | Cost$ AddCounter<0/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ g_3_3_dinosaur_trample | TokenOwner$ You | SpellDescription$ Create a 3/3 green Dinosaur creature token with trample. # TODO: The AI never uses the Ultimate ability (most likely doesn't have the required logic for it) -A:AB$ DealDamage | Cost$ SubCounter | Announce$ X | XMaxLimit$ L | NumDmg$ X | References$ X,L | Planeswalker$ True | Ultimate$ True | ValidTgts$ Creature | TgtPrompt$ Select any number of target creatures | TargetMin$ 1 | TargetMax$ X | DividedAsYouChoose$ X | RememberDamaged$ True | SubAbility$ DBNoBlock | SpellDescription$ CARDNAME deals X damage divided as you choose among any number of target creatures. Creatures dealt damage this way can't block this turn. +A:AB$ DealDamage | Cost$ SubCounter | Announce$ X | NumDmg$ X | References$ X | Planeswalker$ True | Ultimate$ True | ValidTgts$ Creature | TgtPrompt$ Select any number of target creatures | TargetMin$ 1 | TargetMax$ X | DividedAsYouChoose$ X | RememberDamaged$ True | SubAbility$ DBNoBlock | SpellDescription$ CARDNAME deals X damage divided as you choose among any number of target creatures. Creatures dealt damage this way can't block this turn. SVar:DBNoBlock:DB$ Pump | KW$ HIDDEN CARDNAME can't block. | Defined$ Remembered | SubAbility$ DBCleanup | StackDescription$ Creatures dealt damage this way can't block this turn. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$XChoice -SVar:L:Count$CardCounters.LOYALTY DeckHas:Ability$LifeGain & Ability$Token Oracle:[+2]: You gain life equal to the greatest power among creatures you control.\n[0]: Create a 3/3 green Dinosaur creature token with trample.\n[-X]: Huatli, Warrior Poet deals X damage divided as you choose among any number of target creatures. Creatures dealt damage this way can't block this turn. diff --git a/forge-gui/res/cardsfolder/n/novijen_sages.txt b/forge-gui/res/cardsfolder/n/novijen_sages.txt index 1475498019e..2fa0c728d6e 100644 --- a/forge-gui/res/cardsfolder/n/novijen_sages.txt +++ b/forge-gui/res/cardsfolder/n/novijen_sages.txt @@ -3,8 +3,7 @@ ManaCost:4 U U Types:Creature Human Advisor Mutant PT:0/0 K:Graft:4 -A:AB$ Draw | Cost$ 1 SubCounter<2/P1P1/Creature/among creatures you control> | NumCards$ 1 | SpellDescription$ Draw a card. +A:AB$ Draw | Cost$ 1 RemoveAnyCounter<2/P1P1/Creature.YouCtrl/among creatures you control> | NumCards$ 1 | SpellDescription$ Draw a card. DeckNeeds:Ability$Counters DeckHas:Ability$Counters -SVar:Picture:http://www.wizards.com/global/images/magic/general/novijen_sages.jpg Oracle:Graft 4 (This creature enters the battlefield with four +1/+1 counters on it. Whenever another creature enters the battlefield, you may move a +1/+1 counter from this creature onto it.)\n{1}, Remove two +1/+1 counters from among creatures you control: Draw a card. diff --git a/forge-gui/res/cardsfolder/o/ooze_flux.txt b/forge-gui/res/cardsfolder/o/ooze_flux.txt index 0bfc0039635..3e1161d798f 100644 --- a/forge-gui/res/cardsfolder/o/ooze_flux.txt +++ b/forge-gui/res/cardsfolder/o/ooze_flux.txt @@ -1,9 +1,8 @@ Name:Ooze Flux ManaCost:3 G Types:Enchantment -A:AB$ Token | Announce$ X | Cost$ XCantBe0 1 G SubCounter | TokenAmount$ 1 | TokenScript$ g_x_x_ooze | TokenOwner$ You | LegacyImage$ g x x ooze gtc | TokenPower$ X | TokenToughness$ X | SpellDescription$ Create an X/X green Ooze creature token, where X is the number of +1/+1 counters removed this way. +A:AB$ Token | Cost$ XCantBe0 1 G RemoveAnyCounter | TokenAmount$ 1 | TokenScript$ g_x_x_ooze | TokenOwner$ You | LegacyImage$ g x x ooze gtc | TokenPower$ X | TokenToughness$ X | SpellDescription$ Create an X/X green Ooze creature token, where X is the number of +1/+1 counters removed this way. SVar:X:Count$xPaid AI:RemoveDeck:All DeckHints:Ability$Counters -SVar:Picture:http://www.wizards.com/global/images/magic/general/ooze_flux.jpg Oracle:{1}{G}, Remove one or more +1/+1 counters from among creatures you control: Create an X/X green Ooze creature token, where X is the number of +1/+1 counters removed this way. diff --git a/forge-gui/res/cardsfolder/p/petalmane_baku.txt b/forge-gui/res/cardsfolder/p/petalmane_baku.txt index 07bbc0fb932..78deace7265 100644 --- a/forge-gui/res/cardsfolder/p/petalmane_baku.txt +++ b/forge-gui/res/cardsfolder/p/petalmane_baku.txt @@ -3,11 +3,9 @@ ManaCost:1 G Types:Creature Spirit PT:1/2 T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may put a ki counter on CARDNAME. -SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 -#ChosenX SVar created by Cost payment -A:AB$ Mana | Cost$ 1 SubCounter | Produced$ Any | Amount$ ChosenX | References$ X | AILogic$ ManaRitual | AINoRecursiveCheck$ True | SpellDescription$ Add X mana of any one color. -SVar:X:XChoice +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 +A:AB$ Mana | Cost$ 1 SubCounter | Produced$ Any | Amount$ X | References$ X | AILogic$ ManaRitual | AINoRecursiveCheck$ True | SpellDescription$ Add X mana of any one color. +SVar:X:Count$xPaid AI:RemoveDeck:Random DeckHints:Type$Spirit|Arcane -SVar:Picture:http://www.wizards.com/global/images/magic/general/petalmane_baku.jpg Oracle:Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Petalmane Baku.\n{1}, Remove X ki counters from Petalmane Baku: Add X mana of any one color. diff --git a/forge-gui/res/cardsfolder/p/power_conduit.txt b/forge-gui/res/cardsfolder/p/power_conduit.txt index 60fdd24c135..cc83bc5695e 100644 --- a/forge-gui/res/cardsfolder/p/power_conduit.txt +++ b/forge-gui/res/cardsfolder/p/power_conduit.txt @@ -1,9 +1,8 @@ Name:Power Conduit ManaCost:2 Types:Artifact -A:AB$ Charm | Cost$ T RemoveAnyCounter<1/Permanent.YouCtrl/a permanent you control> | Choices$ ConduitCharge,ConduitP1P1 | Defined$ You +A:AB$ Charm | Cost$ T RemoveAnyCounter<1/Any/Permanent.YouCtrl/a permanent you control> | Choices$ ConduitCharge,ConduitP1P1 | Defined$ You SVar:ConduitCharge:DB$ PutCounter | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | CounterType$ CHARGE | CounterNum$ 1 | SpellDescription$ Put a charge counter on target artifact. SVar:ConduitP1P1:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on target creature. SVar:AIRemoveCounterCostPriority:ANY -SVar:Picture:http://www.wizards.com/global/images/magic/general/power_conduit.jpg Oracle:{T}, Remove a counter from a permanent you control: Choose one —\n• Put a charge counter on target artifact.\n• Put a +1/+1 counter on target creature. diff --git a/forge-gui/res/cardsfolder/q/quillmane_baku.txt b/forge-gui/res/cardsfolder/q/quillmane_baku.txt index ddf79d3bf5e..3f9a9f7e786 100644 --- a/forge-gui/res/cardsfolder/q/quillmane_baku.txt +++ b/forge-gui/res/cardsfolder/q/quillmane_baku.txt @@ -3,10 +3,10 @@ ManaCost:4 U Types:Creature Spirit PT:3/3 T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may put a ki counter on CARDNAME. -SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 -A:AB$ ChangeZone | Cost$ 1 T SubCounter | Origin$ Battlefield | Destination$ Hand | ValidTgts$ Creature | ChangeNum$ 1 | References$ X | AITgtBeforeCostEval$ True | SpellDescription$ Return target creature with converted mana cost X or less to its owner's hand. -SVar:X:Targeted$CardManaCost +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 +A:AB$ ChangeZone | Cost$ 1 T SubCounter | Origin$ Battlefield | Destination$ Hand | ValidTgts$ Creature.cmcLEX | ChangeNum$ 1 | References$ X | SpellDescription$ Return target creature with converted mana cost X or less to its owner's hand. +SVar:X:Count$xPaid AI:RemoveDeck:Random +DeckHints:Type$Spirit|Arcane # We'll need to improve the script at some stage, especially if we add Hunter of Eyeblights or Razorfin Abolisher. -SVar:Picture:http://www.wizards.com/global/images/magic/general/quillmane_baku.jpg Oracle:Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Quillmane Baku.\n{1}, {T}, Remove X ki counters from Quillmane Baku: Return target creature with converted mana cost X or less to its owner's hand. diff --git a/forge-gui/res/cardsfolder/r/retribution_of_the_ancients.txt b/forge-gui/res/cardsfolder/r/retribution_of_the_ancients.txt index 806ab4283aa..a78a1318b96 100644 --- a/forge-gui/res/cardsfolder/r/retribution_of_the_ancients.txt +++ b/forge-gui/res/cardsfolder/r/retribution_of_the_ancients.txt @@ -1,9 +1,8 @@ Name:Retribution of the Ancients ManaCost:B Types:Enchantment -A:AB$ Pump | Announce$ X | Cost$ B SubCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -X | NumDef$ -X | References$ X | IsCurse$ True | SpellDescription$ Target creature gets -X/-X until end of turn. +A:AB$ Pump | Cost$ B RemoveAnyCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -X | NumDef$ -X | References$ X | IsCurse$ True | SpellDescription$ Target creature gets -X/-X until end of turn. SVar:X:Count$xPaid AI:RemoveDeck:All DeckNeeds:Ability$Counters -SVar:Picture:http://www.wizards.com/global/images/magic/general/retribution_of_the_ancients.jpg Oracle:{B}, Remove X +1/+1 counters from among creatures you control: Target creature gets -X/-X until end of turn. diff --git a/forge-gui/res/cardsfolder/s/skullmane_baku.txt b/forge-gui/res/cardsfolder/s/skullmane_baku.txt index 7763a6af192..1dfa5d8e7de 100644 --- a/forge-gui/res/cardsfolder/s/skullmane_baku.txt +++ b/forge-gui/res/cardsfolder/s/skullmane_baku.txt @@ -3,11 +3,9 @@ ManaCost:3 B B Types:Creature Spirit PT:2/1 T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may put a ki counter on CARDNAME. -SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 -#ChosenX SVar created by Cost payment -A:AB$ Pump | Cost$ 1 T SubCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -ChosenX | NumDef$ -ChosenX | IsCurse$ True | AILogic$ DebuffForXCounters | References$ X | SpellDescription$ Target creature gets -X/-X until end of turn. -SVar:X:XChoice +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 +A:AB$ Pump | Cost$ 1 T SubCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ -X | NumDef$ -X | IsCurse$ True | References$ X | SpellDescription$ Target creature gets -X/-X until end of turn. +SVar:X:Count$xPaid AI:RemoveDeck:Random DeckHints:Type$Spirit|Arcane -SVar:Picture:http://www.wizards.com/global/images/magic/general/skullmane_baku.jpg Oracle:Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Skullmane Baku.\n{1}, {T}, Remove X ki counters from Skullmane Baku: Target creature gets -X/-X until end of turn. diff --git a/forge-gui/res/cardsfolder/s/soul_diviner.txt b/forge-gui/res/cardsfolder/s/soul_diviner.txt index ee5b4f516fa..d86e1c17296 100644 --- a/forge-gui/res/cardsfolder/s/soul_diviner.txt +++ b/forge-gui/res/cardsfolder/s/soul_diviner.txt @@ -2,6 +2,6 @@ Name:Soul Diviner ManaCost:U B Types:Creature Zombie Wizard PT:2/3 -A:AB$ Draw | Cost$ T RemoveAnyCounter<1/Card.Artifact;Card.Creature;Card.Land;Card.Planeswalker/artifact, creature, land or planeswalker> | NumCards$ 1 | SpellDescription$ Draw a card. +A:AB$ Draw | Cost$ T RemoveAnyCounter<1/Any/Card.Artifact;Card.Creature;Card.Land;Card.Planeswalker/artifact, creature, land or planeswalker> | NumCards$ 1 | SpellDescription$ Draw a card. AI:RemoveDeck:Random Oracle:{T}, Remove a counter from an artifact, creature, land, or planeswalker you control: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/tayam_luminous_enigma.txt b/forge-gui/res/cardsfolder/upcoming/tayam_luminous_enigma.txt new file mode 100644 index 00000000000..c4c5feea2fa --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tayam_luminous_enigma.txt @@ -0,0 +1,12 @@ +Name:Tayam, Luminous Enigma +ManaCost:1 W B G +Types:Legendary Creature Nightmare Beast +PT:3/3 +K:ETBReplacement:Other:AddExtraCounter:Mandatory:Battlefield:Creature.Other+YouCtrl +SVar:AddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ Vigilance | CounterNum$ 1 | SpellDescription$ Each other creature you control enters the battlefield with an additional vigilance counter on it. +A:AB$ Mill | Cost$ 3 RemoveAnyCounter<3/Any/Permanent.YouCtrl/among permanents you control> | NumCards$ 3 | Defined$ You | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ Mill three cards, then return a permanent card with converted mana cost 3 or less from your graveyard to the battlefield. +SVar:DBChangeZone:DB$ ChangeZone | Hidden$ True | Mandatory$ True | ChangeType$ Permanent.YouOwn+cmcLE3 | ChangeNum$ 1 | Origin$ Graveyard | Destination$ Battlefield +DeckHas:Ability$Counters +SVar:AIRemoveCounterCostPriority:Vigilance +Oracle:Each other creature you control enters the battlefield with an additional vigilance counter on it.\n{3}, Remove three counters from among creatures you control: Mill three cards, then return a permanent card with converted mana cost 3 or less from your graveyard to the battlefield. + diff --git a/forge-gui/res/cardsfolder/w/waxmane_baku.txt b/forge-gui/res/cardsfolder/w/waxmane_baku.txt index 9a7f91d050b..0feb2634a46 100644 --- a/forge-gui/res/cardsfolder/w/waxmane_baku.txt +++ b/forge-gui/res/cardsfolder/w/waxmane_baku.txt @@ -3,10 +3,9 @@ ManaCost:2 W Types:Creature Spirit PT:2/2 T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may put a ki counter on CARDNAME. -SVar:TrigPutCounter:DB$PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 -A:AB$ Tap | Announce$ X | XMaxLimit$ Ki | Cost$ 1 SubCounter | CostDesc$ {1}, Remove X ki counters from CARDNAME: | TargetMin$ X | TargetMax$ X | ValidTgts$ Creature | IsCurse$ True | AILogic$ TapForXCounters | TgtPrompt$ Select X target creatures | References$ X,Ki | SpellDescription$ Tap X target creatures. -SVar:X:XChoice -SVar:Ki:Count$CardCounters.KI +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ KI | CounterNum$ 1 +A:AB$ Tap | Cost$ 1 SubCounter | TargetMin$ X | TargetMax$ X | ValidTgts$ Creature | IsCurse$ True | TgtPrompt$ Select X target creatures | References$ X | SpellDescription$ Tap X target creatures. +SVar:X:Count$xPaid AI:RemoveDeck:Random DeckHints:Type$Spirit|Arcane Oracle:Whenever you cast a Spirit or Arcane spell, you may put a ki counter on Waxmane Baku.\n{1}, Remove X ki counters from Waxmane Baku: Tap X target creatures. diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 15c31774702..baf6bb7a188 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -3,18 +3,18 @@ package forge.player; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; + import forge.card.CardType; import forge.game.Game; import forge.game.GameEntity; +import forge.game.GameEntityCounterTable; import forge.game.GameEntityView; import forge.game.GameEntityViewMap; import forge.game.ability.AbilityUtils; @@ -527,7 +527,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { if (c == null) { c = AbilityUtils.calculateAmount(source, amount, ability); } - + if (!player.getController().confirmPayment(cost, Localizer.getInstance().getMessage("lblDoYouWantFlipNCoinAction", String.valueOf(c)), ability)) { return null; } @@ -907,57 +907,59 @@ public class HumanCostDecision extends CostDecisionMakerBase { c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); } - CardCollectionView list = new CardCollection(player.getCardsIn(ZoneType.Battlefield)); - list = CardLists.getValidCards(list, type.split(";"), player, source, ability); + CardCollectionView list = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), type.split(";"), player, source, ability); + list = CardLists.filter(list, CardPredicates.hasCounters()); - - list = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card card) { - return card.hasCounters(); - } - }); - final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, list, ability); - inp.setMessage(Localizer.getInstance().getMessage("lblSelectTargetCounter", cost.getDescriptiveType())); - inp.setCancelAllowed(false); + final InputSelectCardToRemoveCounter inp = new InputSelectCardToRemoveCounter(controller, c, cost, cost.counter, list, ability); + inp.setCancelAllowed(true); inp.showAndWait(); - final Card selected = inp.getFirstSelected(); - final Map tgtCounters = selected.getCounters(); - final List typeChoices = new ArrayList<>(); - for (final CounterType key : tgtCounters.keySet()) { - if (tgtCounters.get(key) > 0) { - typeChoices.add(key); - } + if (inp.hasCancelled()) { + return null; } - final String prompt = Localizer.getInstance().getMessage("lblSelectRemoveCounterType"); - cost.setCounterType(controller.getGui().one(prompt, typeChoices)); - - return PaymentDecision.card(selected, cost.getCounter()); + return PaymentDecision.counters(inp.getCounterTable()); } - public static final class InputSelectCardToRemoveCounter extends InputSelectManyBase { + public static final class InputSelectCardToRemoveCounter extends InputSelectManyBase { private static final long serialVersionUID = 2685832214519141903L; - private final Map cardsChosen; private final CounterType counterType; private final CardCollectionView validChoices; - public InputSelectCardToRemoveCounter(final PlayerControllerHuman controller, final int cntCounters, final CounterType cType, final CardCollectionView validCards, final SpellAbility sa) { + private final GameEntityCounterTable counterTable = new GameEntityCounterTable(); + + public InputSelectCardToRemoveCounter(final PlayerControllerHuman controller, final int cntCounters, final CostPart costPart, final CounterType cType, final CardCollectionView validCards, final SpellAbility sa) { super(controller, cntCounters, cntCounters, sa); this.validChoices = validCards; counterType = cType; - cardsChosen = cntCounters > 0 ? new HashMap<>() : null; + + setMessage(Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", "%d", counterType == null ? "any" : counterType.getName(), costPart.getDescriptiveType())); } @Override protected boolean onCardSelected(final Card c, final List otherCardsToSelect, final ITriggerEvent triggerEvent) { - if (!isValidChoice(c) || c.getCounters(counterType) <= getTimesSelected(c)) { + if (!isValidChoice(c)) { return false; } - final int tc = getTimesSelected(c); - cardsChosen.put(c, tc + 1); + CounterType cType = this.counterType; + if (cType == null) { + Map cmap = counterTable.filterToRemove(c); + + String prompt = Localizer.getInstance().getMessage("lblSelectCountersTypeToRemove"); + + cType = getController().chooseCounterType(Lists.newArrayList(cmap.keySet()), sa, prompt, null); + } + + if (cType == null) { + return false; + } + + if (c.getCounters(cType) <= counterTable.get(c, cType)) { + return false; + } + + counterTable.put(c, cType, 1); onSelectStateChanged(c, true); refresh(); @@ -966,9 +968,25 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public String getActivateAction(final Card c) { - if (!isValidChoice(c) || c.getCounters(counterType) <= getTimesSelected(c)) { + if (!isValidChoice(c)) { return null; } + if (counterType != null) { + if (c.getCounters(counterType) <= counterTable.get(c, counterType)) { + return null; + } + } else { + boolean found = false; + for (Map.Entry e : c.getCounters().entrySet()) { + if (e.getValue() > counterTable.get(c, e.getKey())) { + found = true; + break; + } + } + if (!found) { + return null; + } + } return Localizer.getInstance().getMessage("lblRemoveCounterFromCard"); } @@ -992,9 +1010,11 @@ public class HumanCostDecision extends CostDecisionMakerBase { private int getDistibutedCounters() { int sum = 0; - for (final Entry kv : cardsChosen.entrySet()) { - sum += kv.getValue().intValue(); + + for (Integer v : this.counterTable.values()) { + sum += v; } + return sum; } @@ -1002,13 +1022,13 @@ public class HumanCostDecision extends CostDecisionMakerBase { return validChoices.contains(choice); } - public int getTimesSelected(final Card c) { - return cardsChosen.containsKey(c) ? cardsChosen.get(c).intValue() : 0; + public GameEntityCounterTable getCounterTable() { + return this.counterTable; } @Override - public Collection getSelected() { - return cardsChosen.keySet(); + public Collection getSelected() { + return counterTable.rowKeySet(); } } @@ -1063,45 +1083,28 @@ public class HumanCostDecision extends CostDecisionMakerBase { return PaymentDecision.card(ability.getOriginalHost(), cntRemoved >= 0 ? cntRemoved : maxCounters); } - final CardCollectionView validCards = CardLists.getValidCards(player.getCardsIn(cost.zone), type.split(";"), player, source, ability); - if (cost.zone.equals(ZoneType.Battlefield)) { - if (cntRemoved == 0) { - return PaymentDecision.card(source, 0); - } - - final InputSelectCardToRemoveCounter inp = new InputSelectCardToRemoveCounter(controller, cntRemoved, cost.counter, validCards, ability); - inp.setMessage(Localizer.getInstance().getMessage("lblRemoveNTargetCounterFromCardPayCostConfirm", "%d", cost.counter.getName(), cost.getDescriptiveType())); - inp.setCancelAllowed(true); - inp.showAndWait(); - if (inp.hasCancelled()) { - return null; - } - - // Have to hack here: remove all counters minus one, without firing any triggers, - // triggers will fire when last is removed by executePayment. - // They don't care how many were removed anyway - // int sum = 0; - for (final Card crd : inp.getSelected()) { - final int removed = inp.getTimesSelected(crd); - // sum += removed; - if (removed < 2) { - continue; - } - final int oldVal = crd.getCounters().get(cost.counter).intValue(); - crd.getCounters().put(cost.counter, Integer.valueOf(oldVal - removed + 1)); - } - return PaymentDecision.card(inp.getSelected(), 1); - } - - // Rift Elemental only - always removes 1 counter, so there will be no code for N counters. - GameEntityViewMap gameCacheSuspended = GameEntityView.getMap(CardLists.filter(validCards, CardPredicates.hasCounter(cost.counter))); - - final CardView cv = controller.getGui().oneOrNone(Localizer.getInstance().getMessage("lblRemoveCountersFromAInZoneCard", cost.zone.getTranslatedName()), gameCacheSuspended.getTrackableKeys()); - if (cv == null || !gameCacheSuspended.containsKey(cv)) { + CardCollectionView validCards = CardLists.getValidCards(player.getCardsIn(cost.zone), type.split(";"), player, source, ability); + // you can only select 1 card to remove N counters from + validCards = CardLists.filter(validCards, CardPredicates.hasCounter(cost.counter, cntRemoved)); + if (validCards.isEmpty()) { return null; } - return PaymentDecision.card(gameCacheSuspended.get(cv), c); + final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, validCards, ability); + inp.setMessage(Localizer.getInstance().getMessage("lblRemoveCountersFromAInZoneCard", cost.zone.getTranslatedName())); + inp.setCancelAllowed(true); + inp.showAndWait(); + + if (inp.hasCancelled()) { + return null; + } + + final Card selected = inp.getFirstSelected(); + if (selected == null) { + return null; + } + + return PaymentDecision.card(selected, cntRemoved); } @Override diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 2cae915eb2d..5663e55fc40 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -1,6 +1,6 @@ package forge.player; -import com.google.common.base.Predicate; + import com.google.common.collect.Iterables; import forge.FThreads; import forge.card.mana.ManaCost; @@ -29,12 +29,10 @@ import forge.util.TextUtil; import forge.util.collect.FCollectionView; import forge.util.gui.SGuiChoose; import forge.util.Localizer; -import forge.util.CardTranslation; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; -import java.util.Map; public class HumanPlay { @@ -362,74 +360,20 @@ public class HumanPlay { part.payAsDecided(p, pd, sourceAbility); } else if (part instanceof CostRemoveCounter) { - CounterType counterType = ((CostRemoveCounter) part).counter; - int amount = getAmountFromPartX(part, source, sourceAbility); + PaymentDecision pd = part.accept(hcd); - if (!part.canPay(sourceAbility, p)) { + if (pd == null) return false; - } - - if (!mandatory) { - if (!p.getController().confirmPayment(part, Localizer.getInstance().getMessage("lblDoYouWantRemoveNTargetTypeCounterFromCard", String.valueOf(amount), counterType.getName(), CardTranslation.getTranslatedName(source.getName())), sourceAbility)) { - return false; - } - } - - source.subtractCounter(counterType, amount); + else + part.payAsDecided(p, pd, sourceAbility); } else if (part instanceof CostRemoveAnyCounter) { - int amount = getAmountFromPartX(part, source, sourceAbility); - CardCollectionView list = p.getCardsIn(ZoneType.Battlefield); - int allCounters = 0; - for (Card c : list) { - final Map tgtCounters = c.getCounters(); - for (Integer value : tgtCounters.values()) { - allCounters += value; - } - } - if (allCounters < amount) { return false; } + PaymentDecision pd = part.accept(hcd); - if (!mandatory) { - if (!p.getController().confirmPayment(part, Localizer.getInstance().getMessage("lblDoYouWantRemoveCountersFromCard", part.getDescriptiveType()), sourceAbility)) { - return false; - } - } - - list = CardLists.getValidCards(list, part.getType().split(";"), p, source, sourceAbility); - while (amount > 0) { - final CounterType counterType; - list = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card card) { - return card.hasCounters(); - } - }); - if (list.isEmpty()) { return false; } - InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, list, sourceAbility); - inp.setMessage(Localizer.getInstance().getMessage("lblSelectRemoveCounterCard")); - inp.setCancelAllowed(true); - inp.showAndWait(); - if (inp.hasCancelled()) { - continue; - } - Card selected = inp.getFirstSelected(); - final Map tgtCounters = selected.getCounters(); - final List typeChoices = new ArrayList<>(); - for (CounterType key : tgtCounters.keySet()) { - if (tgtCounters.get(key) > 0) { - typeChoices.add(key); - } - } - if (typeChoices.size() > 1) { - String cprompt = Localizer.getInstance().getMessage("lblSelectRemoveCounterType"); - counterType = controller.getGui().one(cprompt, typeChoices); - } - else { - counterType = typeChoices.get(0); - } - selected.subtractCounter(counterType, 1); - amount--; - } + if (pd == null) + return false; + else + part.payAsDecided(p, pd, sourceAbility); } else if (part instanceof CostExile) { CostExile costExile = (CostExile) part; diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 493512562a0..499532bc431 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -29,7 +29,6 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardPlayOption; import forge.game.cost.Cost; -import forge.game.cost.CostPart; import forge.game.cost.CostPartMana; import forge.game.cost.CostPayment; import forge.game.keyword.KeywordInterface; @@ -256,16 +255,7 @@ public class HumanPlaySpellAbility { } if (needX && manaCost != null) { - boolean xInCost = manaCost.getAmountOfX() > 0; - if (!xInCost) { - for (final CostPart part : cost.getCostParts()) { - if (part.getAmount().equals("X")) { - xInCost = true; - break; - } - } - } - if (xInCost) { + if (cost.hasXInAnyCostPart()) { final String sVar = ability.getSVar("X"); //only prompt for new X value if card doesn't determine it another way if ("Count$xPaid".equals(sVar) || sVar.isEmpty()) { final Integer value = controller.announceRequirements(ability, "X", allowZero && manaCost.canXbe0()); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 1e1130d13ba..6ee56cc6811 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -321,8 +321,27 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont public Integer announceRequirements(final SpellAbility ability, final String announce, final boolean canChooseZero) { final int min = canChooseZero ? 0 : 1; - final int max = ability.hasParam("XMaxLimit") ? AbilityUtils.calculateAmount(ability.getHostCard(), - ability.getParam("XMaxLimit"), ability) : Integer.MAX_VALUE; + int max = Integer.MAX_VALUE; + + if ("X".equals(announce)) { + Cost cost = ability.getPayCosts(); + if (ability.hasParam("XMaxLimit")) { + max = Math.min(max, AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("XMaxLimit"), ability)); + } + if (cost != null) { + Integer costX = cost.getMaxForNonManaX(ability, player); + if (costX != null) { + max = Math.min(max, min); + } + } + } + + if (ability.usesTargeting()) { + // if announce is used as min targets, check what the max possible number would be + if (announce.equals(ability.getTargetRestrictions().getMinTargets())) { + max = Math.min(max, CardUtil.getValidCardsToTarget(ability.getTargetRestrictions(), ability).size()); + } + } return getGui().getInteger(localizer.getMessage("lblChooseAnnounceForCard", announce, CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max, min + 9); } diff --git a/forge-gui/src/main/java/forge/player/TargetSelection.java b/forge-gui/src/main/java/forge/player/TargetSelection.java index 3463c6e457d..f62b1a41e29 100644 --- a/forge-gui/src/main/java/forge/player/TargetSelection.java +++ b/forge-gui/src/main/java/forge/player/TargetSelection.java @@ -71,11 +71,10 @@ public class TargetSelection { } public final boolean chooseTargets(Integer numTargets) { - final TargetRestrictions tgt = getTgt(); - final boolean canTarget = tgt != null && tgt.doesTarget(); - if (!canTarget) { + if (!ability.usesTargeting()) { throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability); } + final TargetRestrictions tgt = getTgt(); // Number of targets is explicitly set only if spell is being redirected (ex. Swerve or Redirect) final int minTargets = numTargets != null ? numTargets.intValue() : tgt.getMinTargets(ability.getHostCard(), ability);