diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 9cefd9aa315..8054146ab47 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -135,16 +135,20 @@ public class AiAttackController { return !c.isTapped() && !c.isCreature() && !c.isPlaneswalker(); } }; + + CardCollection tappedDefenders = new CardCollection(); for (Card c : CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), canAnimate)) { - /* TODO: is the commented out code still necessary for anything? - if (c.isToken() && c.getCopiedPermanent() == null && !c.canTransform(null)) { - continue; - } - */ for (SpellAbility sa : Iterables.filter(c.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.Animate))) { - if (ComputerUtilCost.canPayCost(sa, defender, false) - && sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) { - Card animatedCopy = AnimateAi.becomeAnimated(c, sa); + if (sa.usesTargeting() || !sa.getParamOrDefault("Defined", "Self").equals("Self")) { + continue; + } + if (sa.hasParam("Crew") && !ComputerUtilCost.checkTapTypeCost(defender, sa.getPayCosts(), c, sa, tappedDefenders)) { + continue; + } else if (!ComputerUtilCost.canPayCost(sa, defender, false) || !sa.getRestrictions().checkOtherRestrictions(c, sa, defender)) { + continue; + } + Card animatedCopy = AnimateAi.becomeAnimated(c, sa); + if (animatedCopy.isCreature()) { int saCMC = sa.getPayCosts() != null && sa.getPayCosts().hasManaCost() ? sa.getPayCosts().getTotalMana().getCMC() : 0; // FIXME: imprecise, only works 100% for colorless mana if (totalMana - manaReserved >= saCMC) { @@ -153,6 +157,8 @@ public class AiAttackController { } } } + defenders.removeAll(tappedDefenders); + // Transform (e.g. Incubator tokens) for (SpellAbility sa : Iterables.filter(c.getSpellAbilities(), SpellAbilityPredicates.isApi(ApiType.SetState))) { Card transformedCopy = ComputerUtilCombat.canTransform(c); diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java index 37114291098..c3e3c77eac7 100644 --- a/forge-ai/src/main/java/forge/ai/AiBlockController.java +++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java @@ -1109,7 +1109,9 @@ public class AiBlockController { } // TODO could be made more accurate if this would be inside each blocker choosing loop instead - lifeInDanger |= removeUnpayableBlocks(combat) && ComputerUtilCombat.lifeInDanger(ai, combat); + if (removeUnpayableBlocks(combat) || lifeInDanger) { + lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat); + } // == 2. If the AI life would still be in danger make a safer approach == if (lifeInDanger) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 3ef898ea51f..310ae5fb8ab 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -694,10 +694,8 @@ public class ComputerUtil { public static CardCollection chooseTapType(final Player ai, final String type, final Card activate, final boolean tap, final int amount, final CardCollectionView exclude, SpellAbility sa) { CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield)); all.removeAll(exclude); - CardCollection typeList = - CardLists.getValidCards(all, type.split(";"), activate.getController(), activate, sa); + CardCollection typeList = CardLists.getValidCards(all, type.split(";"), activate.getController(), activate, sa); - // is this needed? typeList = CardLists.filter(typeList, Presets.UNTAPPED); if (tap) { @@ -733,7 +731,6 @@ public class ComputerUtil { typeList = CardLists.getNotKeyword(typeList, "CARDNAME can't crew Vehicles."); } - // is this needed? typeList = CardLists.filter(typeList, Presets.UNTAPPED); if (tap) { @@ -772,7 +769,6 @@ public class ComputerUtil { CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa); - // is this needed? typeList = CardLists.filter(typeList, Presets.TAPPED); if (untap) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 1813d70e50b..f9ca43b15a5 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -415,7 +415,7 @@ public class ComputerUtilCost { * the source * @return true, if successful */ - public static boolean checkTapTypeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sa) { + public static boolean checkTapTypeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sa, final CardCollection alreadyTapped) { if (cost == null) { return true; } @@ -442,8 +442,13 @@ public class ComputerUtilCost { return ComputerUtilCard.evaluateCreature(c) >= vehicleValue; } }); // exclude creatures >= vehicle - return ComputerUtil.chooseTapTypeAccumulatePower(ai, type, sa, true, - Integer.parseInt(totalP), exclude) != null; + exclude.addAll(alreadyTapped); + CardCollection tappedCrew = ComputerUtil.chooseTapTypeAccumulatePower(ai, type, sa, true, Integer.parseInt(totalP), exclude); + if (tappedCrew != null) { + alreadyTapped.addAll(tappedCrew); + return true; + } + return false; } // check if we have a valid card to tap (e.g. Jaspera Sentinel) @@ -480,36 +485,6 @@ public class ComputerUtilCost { return true; } - /** - *

- * shouldPayCost. - *

- * - * @param hostCard - * a {@link forge.game.card.Card} object. - * @param cost - * @return a boolean. - */ - @Deprecated - public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) { - for (final CostPart part : cost.getCostParts()) { - if (part instanceof CostPayLife) { - if (!ai.cantLoseForZeroOrLessLife()) { - continue; - } - final int remainingLife = ai.getLife(); - final int lifeCost = part.convertAmount(); - if ((remainingLife - lifeCost) < 10) { - return false; //Don't pay life if it would put AI under 10 life - } else if ((remainingLife / lifeCost) < 4) { - return false; //Don't pay life if it is more than 25% of current life - } - } - } - - return true; - } // shouldPayCost() - /** *

* canPayCost. @@ -580,15 +555,6 @@ public class ComputerUtilCost { } } - // KLD vehicle - if (sa.hasParam("Crew")) { // put under checkTapTypeCost? - for (final CostPart part : sa.getPayCosts().getCostParts()) { - if (part instanceof CostTapType && part.getType().contains("+withTotalPowerGE")) { - return new AiCostDecision(player, sa, false).visit((CostTapType)part) != null; - } - } - } - // Ward - will be accounted for when rechecking a targeted ability if (!sa.isTrigger() && sa.usesTargeting() && (!sa.isSpell() || !cannotBeCountered)) { for (Card tgt : sa.getTargets().getTargetCards()) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 586801b37cb..e157a645d23 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -312,7 +312,7 @@ public class ComputerUtilMana { continue; } - if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa)) { + if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, new CardCollection())) { continue; } diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java index 87c163f181c..9241e07133e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java @@ -130,7 +130,7 @@ public class AnimateAi extends SpellAbilityAi { return true; // interrupt sacrifice } } - if (!ComputerUtilCost.checkTapTypeCost(aiPlayer, sa.getPayCosts(), source, sa)) { + if (!ComputerUtilCost.checkTapTypeCost(aiPlayer, sa.getPayCosts(), source, sa, new CardCollection())) { return false; // prevent crewing with equal or better creatures } @@ -379,7 +379,7 @@ public class AnimateAi extends SpellAbilityAi { return copy; } - public static void becomeAnimated(final Card card, final boolean hasOriginalCardSickness, final SpellAbility sa) { + private static void becomeAnimated(final Card card, final boolean hasOriginalCardSickness, final SpellAbility sa) { // duplicating AnimateEffect.resolve final Card source = sa.getHostCard(); final Game game = sa.getActivatingPlayer().getGame(); 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 78341e0afbc..6a608655be6 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java @@ -78,15 +78,7 @@ public class SacrificeAi extends SpellAbilityAi { String num = sa.getParamOrDefault("Amount" , "1"); final int amount = AbilityUtils.calculateAmount(source, num, sa); - List list = null; - try { - list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); - } catch (NullPointerException e) { - return false; - } finally { - if (list == null) - return false; - }//prevent NPE on MoJhoSto + List list = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); for (Card c : list) { if (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) > 3) { @@ -140,30 +132,13 @@ public class SacrificeAi extends SpellAbilityAi { amount = Math.min(ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()), amount); } - List humanList = null; - try { - humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); - } catch (NullPointerException e) { - return false; - } finally { - if (humanList == null) - return false; - }//prevent NPE on MoJhoSto + List humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); // Since all of the cards have AI:RemoveDeck:All, I enabled 1 for 1 // (or X for X) trades for special decks return humanList.size() >= amount; } else if (defined.equals("You")) { - List computerList = null; - try { - computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); - } catch (NullPointerException e) { - return false; - } finally { - if (computerList == null) - return false; - }//prevent NPE on MoJhoSto - + List computerList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, sa.getActivatingPlayer(), source, sa); for (Card c : computerList) { if (c.hasSVar("SacMe") || ComputerUtilCard.evaluateCreature(c) <= 135) { return true; diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index 275aa4ea28c..f12bae53c49 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -2180,34 +2180,20 @@ public class AbilityUtils { return doXMath(Integer.parseInt(sq[isMyMain ? 1 : 2]), expr, c, ctb); } - // Count$AttachedTo + // Count$AttachedTo if (sq[0].startsWith("AttachedTo")) { final String[] k = l[0].split(" "); - int sum = 0; - for (Card card : getDefinedCards(c, k[1], ctb)) { - // Hateful Eidolon: the script uses LKI so that the attached cards have to be defined - // This card needs the spellability ("Auras You control", you refers to the activating player) - // CardFactoryUtils.xCount doesn't have the sa parameter, SVar:X:TriggeredCard$Valid cannot handle this - sum += CardLists.getValidCardCount(card.getAttachedCards(), k[2], player, c, ctb); - } + int sum = CardLists.getValidCardCount(c.getAttachedCards(), k[1], player, c, ctb); return doXMath(sum, expr, c, ctb); } // Count$CardManaCost if (sq[0].contains("CardManaCost")) { - Card ce; - if (sq[0].contains("Remembered")) { - ce = (Card) c.getFirstRemembered(); - } - else { - ce = c; - } + int cmc = c.getCMC(); - int cmc = ce == null ? 0 : ce.getCMC(); - - if (sq[0].contains("LKI") && ctb instanceof SpellAbility && ce != null && !ce.isInZone(ZoneType.Stack) && ce.getManaCost() != null) { + if (sq[0].contains("LKI") && ctb instanceof SpellAbility && !c.isInZone(ZoneType.Stack) && c.getManaCost() != null) { if (((SpellAbility) ctb).getXManaCostPaid() != null) { - cmc += ((SpellAbility) ctb).getXManaCostPaid() * ce.getManaCost().countX(); + cmc += ((SpellAbility) ctb).getXManaCostPaid() * c.getManaCost().countX(); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java index fd07c116f0e..0af2943d657 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java @@ -33,7 +33,6 @@ public class ChangeTargetsEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { final List sas = getTargetSpells(sa); - final boolean remember = sa.hasParam("RememberTargetedCard"); final Player activator = sa.getActivatingPlayer(); final Player chooser = sa.hasParam("Chooser") ? getDefinedPlayersOrTargeted(sa, "Chooser").get(0) : sa.getActivatingPlayer(); @@ -140,9 +139,6 @@ public class ChangeTargetsEffect extends SpellAbilityEffect { changingTgtSI = changingTgtSI.getSubInstance(); } } - if (remember) { - sa.getHostCard().addRemembered(tgtSA.getHostCard()); - } } } } diff --git a/forge-gui/res/cardsfolder/a/ancient_imperiosaur.txt b/forge-gui/res/cardsfolder/a/ancient_imperiosaur.txt index 797a306646f..b1a76f38cb2 100644 --- a/forge-gui/res/cardsfolder/a/ancient_imperiosaur.txt +++ b/forge-gui/res/cardsfolder/a/ancient_imperiosaur.txt @@ -5,7 +5,7 @@ PT:6/6 K:Convoke K:Trample K:Ward:2 -K:etbCounter:P1P1:X +K:etbCounter:P1P1:X:no condition:CARDNAME enters the battlefield with two +1/+1 counters on it for each creature that convoked it. SVar:X:Convoked$Amount/Twice DeckHas:Ability$Counters Oracle:Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)\nTrample, ward {2}\nAncient Imperiosaur enters the battlefield with two +1/+1 counters on it for each creature that convoked it. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/e/elminster.txt b/forge-gui/res/cardsfolder/e/elminster.txt index dbc18744723..beae73e69ee 100644 --- a/forge-gui/res/cardsfolder/e/elminster.txt +++ b/forge-gui/res/cardsfolder/e/elminster.txt @@ -15,7 +15,7 @@ SVar:DBScry:DB$ Scry | ScryNum$ 2 A:AB$ Dig | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | Reveal$ True | SubAbility$ DBToken | SpellDescription$ Exile the top card of your library. SVar:DBToken:DB$ Token | TokenAmount$ Z | TokenScript$ u_1_1_faerie_dragon_flying | SubAbility$ DBCleanup | SpellDescription$ Create a number of 1/1 blue Faerie Dragon creature tokens with flying equal to that card's mana value. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:Z:Count$RememberedCardManaCost +SVar:Z:Remembered$CardManaCost Text:CARDNAME can be your commander. DeckHas:Ability$Token & Type$Faerie|Dragon DeckHints:Type$Instant|Sorcery diff --git a/forge-gui/res/cardsfolder/h/hateful_eidolon.txt b/forge-gui/res/cardsfolder/h/hateful_eidolon.txt index 19f8dfc8963..2bba2658954 100644 --- a/forge-gui/res/cardsfolder/h/hateful_eidolon.txt +++ b/forge-gui/res/cardsfolder/h/hateful_eidolon.txt @@ -5,6 +5,6 @@ PT:1/2 K:Lifelink T:Mode$ ChangesZone | ValidCard$ Creature.enchanted | Origin$ Battlefield | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever an enchanted creature dies, draw a card for each Aura you controlled that was attached to it. SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ X -SVar:X:Count$AttachedTo TriggeredCardLKICopy Aura.YouCtrl +SVar:X:TriggeredCard$AttachedTo Aura.YouCtrl SVar:EnchantMe:Multiple Oracle:Lifelink\nWhenever an enchanted creature dies, draw a card for each Aura you controlled that was attached to it. diff --git a/forge-gui/res/cardsfolder/i/imps_mischief.txt b/forge-gui/res/cardsfolder/i/imps_mischief.txt index 2b58a115052..2cf7f8ab434 100644 --- a/forge-gui/res/cardsfolder/i/imps_mischief.txt +++ b/forge-gui/res/cardsfolder/i/imps_mischief.txt @@ -1,9 +1,8 @@ Name:Imp's Mischief ManaCost:1 B Types:Instant -A:SP$ ChangeTargets | TargetType$ Spell.singleTarget | ValidTgts$ Card | TgtPrompt$ Select target spell with a single target | RememberTargetedCard$ True | SubAbility$ DBLoseLife | SpellDescription$ Change the target of target spell with a single target. You lose life equal to that spell's mana value. -SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ X | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Remembered$CardManaCost +A:SP$ ChangeTargets | TargetType$ Spell.singleTarget | ValidTgts$ Card | TgtPrompt$ Select target spell with a single target | SubAbility$ DBLoseLife | SpellDescription$ Change the target of target spell with a single target. You lose life equal to that spell's mana value. +SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ X +SVar:X:Targeted$CardManaCost AI:RemoveDeck:All Oracle:Change the target of target spell with a single target. You lose life equal to that spell's mana value. diff --git a/forge-gui/res/cardsfolder/l/living_lore.txt b/forge-gui/res/cardsfolder/l/living_lore.txt index 7e7c174ec2e..a7a95cb4714 100644 --- a/forge-gui/res/cardsfolder/l/living_lore.txt +++ b/forge-gui/res/cardsfolder/l/living_lore.txt @@ -5,7 +5,7 @@ PT:*/* K:ETBReplacement:Copy:ChooseSpell SVar:ChooseSpell:DB$ ChangeZone | ChangeType$ Instant.YouOwn,Sorcery.YouOwn | ChangeNum$ 1 | Hidden$ True | Origin$ Graveyard | Destination$ Exile | RememberChanged$ True | Mandatory$ True | SpellDescription$ As CARDNAME enters the battlefield, exile an instant or sorcery card from your graveyard. S:Mode$ Continuous | EffectZone$ All | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the exiled card's mana value. -SVar:X:Count$RememberedCardManaCost +SVar:X:Remembered$CardManaCost T:Mode$ DamageDealtOnce | CombatDamage$ True | ValidSource$ Card.Self | Execute$ TrigSacLore | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage, you may sacrifice it. If you do, you may cast the exiled card without paying its mana cost. SVar:TrigSacLore:AB$ Play | Cost$ Sac<1/CARDNAME> | Defined$ Remembered | Amount$ All | Controller$ You | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | ForgetRemembered$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.Self | Execute$ DBCleanup diff --git a/forge-gui/res/cardsfolder/s/spark_of_creativity.txt b/forge-gui/res/cardsfolder/s/spark_of_creativity.txt index 98074e396c7..6822a3c4624 100644 --- a/forge-gui/res/cardsfolder/s/spark_of_creativity.txt +++ b/forge-gui/res/cardsfolder/s/spark_of_creativity.txt @@ -8,6 +8,6 @@ SVar:SparkDamage:DB$ DealDamage | Defined$ ParentTarget | NumDmg$ X | SpellDescr SVar:SparkPlay:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play | ExileOnMoved$ Exile | SpellDescription$ You may play exiled card until end of turn. SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play remembered card. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$RememberedCardManaCost +SVar:X:Remembered$CardManaCost AI:RemoveDeck:All Oracle:Choose target creature. Exile the top card of your library. You may have Spark of Creativity deal damage to that creature equal to the exiled card's mana value. If you don't, you may play that card until end of turn.