From 8fc16744dad94787e0777c83915f6708f92915fe Mon Sep 17 00:00:00 2001 From: Madwand99 Date: Wed, 6 May 2026 19:03:35 -0700 Subject: [PATCH 1/4] Fix AI targeting for Forge of Heroes and similar counter-boon pump wrappers --- .../main/java/forge/ai/SpellAbilityAi.java | 12 +++ .../main/java/forge/ai/ability/PumpAi.java | 81 ++++++++++++++++++- .../java/forge/ai/ability/PumpAiTest.java | 51 ++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java index 4a3bc3994f5..ed293f28a9b 100644 --- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java @@ -259,6 +259,13 @@ protected AiAbilityDecision doTriggerNoCost(final Player aiPlayer, final SpellAb */ public AiAbilityDecision chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) { final AbilitySub subAb = ab.getSubAbility(); + if (!ab.metConditions() || isDefinedByParentTarget(ab)) { + if (subAb == null) { + return new AiAbilityDecision(100, AiPlayDecision.WillPlay); + } + return chkDrawbackWithSubs(aiPlayer, subAb); + } + AiAbilityDecision decision = SpellApiToAi.Converter.get(ab).chkDrawback(aiPlayer, ab); if (!decision.willingToPlay()) { return decision; @@ -271,6 +278,11 @@ public AiAbilityDecision chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) { return chkDrawbackWithSubs(aiPlayer, subAb); } + private static boolean isDefinedByParentTarget(AbilitySub ab) { + final String defined = ab.getParam("Defined"); + return "ParentTarget".equals(defined) || "Targeted".equals(defined) || "ThisTargetedCard".equals(defined); + } + /** * Handles the AI decision to play a sub-SpellAbility */ 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 c4bc8159c75..e79ef339c27 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -81,6 +81,9 @@ protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, return false; } } + if (hasCounterBoonSubAbility(sa)) { + return ph.is(PhaseType.MAIN2, ai) || isSorcerySpeed(sa, ai); + } return super.checkPhaseRestrictions(ai, sa, ph); } @@ -88,6 +91,9 @@ protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { final Game game = ai.getGame(); boolean main1Preferred = "Main1IfAble".equals(sa.getParam("AILogic")) && ph.is(PhaseType.MAIN1, ai); + if (hasCounterBoonSubAbility(sa)) { + return ph.is(PhaseType.MAIN2, ai) || isSorcerySpeed(sa, ai); + } if (game.getStack().isEmpty() && sa.getPayCosts().hasTapCost()) { if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && ph.isPlayerTurn(ai)) { return false; @@ -352,6 +358,7 @@ private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defe final Game game = ai.getGame(); final Card source = sa.getHostCard(); final boolean isFight = "Fight".equals(sa.getParam("AILogic")) || "PowerDmg".equals(sa.getParam("AILogic")); + final boolean counterBoon = hasCounterBoonSubAbility(sa); immediately = immediately || ComputerUtil.playImmediately(ai, sa); @@ -362,7 +369,8 @@ private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defe && !containsNonCombatKeyword(keywords) && !"UntilYourNextTurn".equals(sa.getParam("Duration")) && !"ReplaySpell".equals(sa.getParam("AILogic")) - && !isFight) { + && !isFight + && !counterBoon) { return false; } @@ -382,6 +390,16 @@ private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defe return true; } + if (counterBoon) { + CardCollection counterBoonTargets = getCounterBoonTargets(ai, sa); + if (counterBoonTargets.isEmpty()) { + return false; + } + + sa.getTargets().add(ComputerUtilCard.getBestAI(counterBoonTargets)); + return true; + } + CardCollection list; if (sa.hasParam("AILogic")) { if (sa.getParam("AILogic").equals("HighestPower") || sa.getParam("AILogic").equals("ContinuousBonus")) { @@ -546,6 +564,67 @@ private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defe return true; } + private CardCollection getCounterBoonTargets(final Player ai, final SpellAbility sa) { + List counterTypes = getCounterBoonTypes(sa); + CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa)); + list = CardLists.filterControlledBy(list, ai.getYourTeam()); + return CardLists.filter(list, c -> canUseCounterBoon(c, counterTypes)); + } + + private boolean hasCounterBoonSubAbility(final SpellAbility sa) { + return !getCounterBoonTypes(sa).isEmpty(); + } + + private List getCounterBoonTypes(final SpellAbility sa) { + List counterTypes = Lists.newArrayList(); + if (sa.isCurse() || sa.hasParam("TargetingPlayer")) { + return counterTypes; + } + + SpellAbility sub = sa.getSubAbility(); + while (sub != null) { + if (!ApiType.PutCounter.equals(sub.getApi()) || !isCounterAddedToPumpTarget(sub)) { + sub = sub.getSubAbility(); + continue; + } + CounterType type = CounterType.getType(sub.getParam("CounterType")); + if (isCounterBoonType(type)) { + counterTypes.add(type); + } + sub = sub.getSubAbility(); + } + return counterTypes; + } + + private boolean isCounterAddedToPumpTarget(final SpellAbility sub) { + String defined = sub.getParam("Defined"); + return "ParentTarget".equals(defined) || "Targeted".equals(defined) || "ThisTargetedCard".equals(defined); + } + + private boolean canUseCounterBoon(final Card card, final List counterTypes) { + for (CounterType type : counterTypes) { + if (!card.canReceiveCounters(type)) { + continue; + } + if (ComputerUtil.isNegativeCounter(type, card) || ComputerUtil.isUselessCounter(type, card)) { + continue; + } + if (type.is(CounterEnumType.P1P1) && !card.isCreature()) { + continue; + } + if (type.is(CounterEnumType.LOYALTY) && !card.isPlaneswalker()) { + continue; + } + return true; + } + return false; + } + + private boolean isCounterBoonType(final CounterType type) { + return type != null && (type.is(CounterEnumType.P1P1) || type.is(CounterEnumType.LOYALTY) + || type.isKeywordCounter()); + } + private boolean pumpMandatoryTarget(final Player ai, final SpellAbility sa) { final TargetRestrictions tgt = sa.getTargetRestrictions(); List list = CardUtil.getValidCardsToTarget(sa); diff --git a/forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java b/forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java new file mode 100644 index 00000000000..212e4a7efe6 --- /dev/null +++ b/forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java @@ -0,0 +1,51 @@ +package forge.ai.ability; + +import forge.ai.AITest; +import forge.ai.AiPlayDecision; +import forge.ai.SpellAbilityAi; +import forge.ai.SpellApiToAi; +import forge.game.Game; +import forge.game.ability.ApiType; +import forge.game.card.Card; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import org.testng.AssertJUnit; +import org.testng.annotations.Test; + +public class PumpAiTest extends AITest { + @Test + public void testCounterBoonWrapperTargetsOwnCommander() { + Game game = initAndCreateGame(); + Player ai = game.getPlayers().get(1); + Player opponent = game.getPlayers().get(0); + ai.setTeam(0); + opponent.setTeam(1); + + Card forge = addCard("Forge of Heroes", ai); + Card aiCommander = addCard("Elvish Mystic", ai); + Card opponentCommander = addCard("Emrakul, the Aeons Torn", opponent); + aiCommander.setCommander(true); + opponentCommander.setCommander(true); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, ai); + game.getAction().checkStateEffects(true); + + SpellAbility forgeSa = findSAWithPrefix(forge, "{T}: Choose target commander"); + AssertJUnit.assertNotNull(forgeSa); + forgeSa.setActivatingPlayer(ai); + AssertJUnit.assertNotNull("Forge of Heroes should have counter sub-abilities", forgeSa.getSubAbility()); + AssertJUnit.assertEquals("P1P1", forgeSa.getSubAbility().getParam("CounterType")); + AssertJUnit.assertEquals("ParentTarget", forgeSa.getSubAbility().getParam("Defined")); + AssertJUnit.assertTrue("AI commander should be a legal Forge of Heroes target", forgeSa.canTarget(aiCommander)); + AssertJUnit.assertTrue("Opponent commander should be a legal Forge of Heroes target", forgeSa.canTarget(opponentCommander)); + + SpellAbilityAi pumpAi = SpellApiToAi.Converter.get(ApiType.Pump); + AiPlayDecision decision = pumpAi.canPlayWithSubs(ai, forgeSa).decision(); + + AssertJUnit.assertEquals("AI should activate Forge of Heroes for its own commander", + AiPlayDecision.WillPlay, decision); + AssertJUnit.assertEquals("AI should not put a beneficial commander counter on an opponent's commander", + aiCommander, forgeSa.getTargetCard()); + } +} From 94a09ea0b14e2e447044fb091a22a76a59b624a8 Mon Sep 17 00:00:00 2001 From: Madwand99 Date: Thu, 7 May 2026 06:51:54 -0700 Subject: [PATCH 2/4] Just fix Forge of Heroes scripting --- .../main/java/forge/ai/SpellAbilityAi.java | 12 --- .../main/java/forge/ai/ability/PumpAi.java | 81 +------------------ .../java/forge/ai/ability/PumpAiTest.java | 30 +++++++ .../res/cardsfolder/f/forge_of_heroes.txt | 2 +- 4 files changed, 32 insertions(+), 93 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java index ed293f28a9b..4a3bc3994f5 100644 --- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java @@ -259,13 +259,6 @@ protected AiAbilityDecision doTriggerNoCost(final Player aiPlayer, final SpellAb */ public AiAbilityDecision chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) { final AbilitySub subAb = ab.getSubAbility(); - if (!ab.metConditions() || isDefinedByParentTarget(ab)) { - if (subAb == null) { - return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } - return chkDrawbackWithSubs(aiPlayer, subAb); - } - AiAbilityDecision decision = SpellApiToAi.Converter.get(ab).chkDrawback(aiPlayer, ab); if (!decision.willingToPlay()) { return decision; @@ -278,11 +271,6 @@ public AiAbilityDecision chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) { return chkDrawbackWithSubs(aiPlayer, subAb); } - private static boolean isDefinedByParentTarget(AbilitySub ab) { - final String defined = ab.getParam("Defined"); - return "ParentTarget".equals(defined) || "Targeted".equals(defined) || "ThisTargetedCard".equals(defined); - } - /** * Handles the AI decision to play a sub-SpellAbility */ 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 e79ef339c27..c4bc8159c75 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -81,9 +81,6 @@ protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, return false; } } - if (hasCounterBoonSubAbility(sa)) { - return ph.is(PhaseType.MAIN2, ai) || isSorcerySpeed(sa, ai); - } return super.checkPhaseRestrictions(ai, sa, ph); } @@ -91,9 +88,6 @@ protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) { final Game game = ai.getGame(); boolean main1Preferred = "Main1IfAble".equals(sa.getParam("AILogic")) && ph.is(PhaseType.MAIN1, ai); - if (hasCounterBoonSubAbility(sa)) { - return ph.is(PhaseType.MAIN2, ai) || isSorcerySpeed(sa, ai); - } if (game.getStack().isEmpty() && sa.getPayCosts().hasTapCost()) { if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && ph.isPlayerTurn(ai)) { return false; @@ -358,7 +352,6 @@ private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defe final Game game = ai.getGame(); final Card source = sa.getHostCard(); final boolean isFight = "Fight".equals(sa.getParam("AILogic")) || "PowerDmg".equals(sa.getParam("AILogic")); - final boolean counterBoon = hasCounterBoonSubAbility(sa); immediately = immediately || ComputerUtil.playImmediately(ai, sa); @@ -369,8 +362,7 @@ private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defe && !containsNonCombatKeyword(keywords) && !"UntilYourNextTurn".equals(sa.getParam("Duration")) && !"ReplaySpell".equals(sa.getParam("AILogic")) - && !isFight - && !counterBoon) { + && !isFight) { return false; } @@ -390,16 +382,6 @@ private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defe return true; } - if (counterBoon) { - CardCollection counterBoonTargets = getCounterBoonTargets(ai, sa); - if (counterBoonTargets.isEmpty()) { - return false; - } - - sa.getTargets().add(ComputerUtilCard.getBestAI(counterBoonTargets)); - return true; - } - CardCollection list; if (sa.hasParam("AILogic")) { if (sa.getParam("AILogic").equals("HighestPower") || sa.getParam("AILogic").equals("ContinuousBonus")) { @@ -564,67 +546,6 @@ private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defe return true; } - private CardCollection getCounterBoonTargets(final Player ai, final SpellAbility sa) { - List counterTypes = getCounterBoonTypes(sa); - CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa)); - list = CardLists.filterControlledBy(list, ai.getYourTeam()); - return CardLists.filter(list, c -> canUseCounterBoon(c, counterTypes)); - } - - private boolean hasCounterBoonSubAbility(final SpellAbility sa) { - return !getCounterBoonTypes(sa).isEmpty(); - } - - private List getCounterBoonTypes(final SpellAbility sa) { - List counterTypes = Lists.newArrayList(); - if (sa.isCurse() || sa.hasParam("TargetingPlayer")) { - return counterTypes; - } - - SpellAbility sub = sa.getSubAbility(); - while (sub != null) { - if (!ApiType.PutCounter.equals(sub.getApi()) || !isCounterAddedToPumpTarget(sub)) { - sub = sub.getSubAbility(); - continue; - } - CounterType type = CounterType.getType(sub.getParam("CounterType")); - if (isCounterBoonType(type)) { - counterTypes.add(type); - } - sub = sub.getSubAbility(); - } - return counterTypes; - } - - private boolean isCounterAddedToPumpTarget(final SpellAbility sub) { - String defined = sub.getParam("Defined"); - return "ParentTarget".equals(defined) || "Targeted".equals(defined) || "ThisTargetedCard".equals(defined); - } - - private boolean canUseCounterBoon(final Card card, final List counterTypes) { - for (CounterType type : counterTypes) { - if (!card.canReceiveCounters(type)) { - continue; - } - if (ComputerUtil.isNegativeCounter(type, card) || ComputerUtil.isUselessCounter(type, card)) { - continue; - } - if (type.is(CounterEnumType.P1P1) && !card.isCreature()) { - continue; - } - if (type.is(CounterEnumType.LOYALTY) && !card.isPlaneswalker()) { - continue; - } - return true; - } - return false; - } - - private boolean isCounterBoonType(final CounterType type) { - return type != null && (type.is(CounterEnumType.P1P1) || type.is(CounterEnumType.LOYALTY) - || type.isKeywordCounter()); - } - private boolean pumpMandatoryTarget(final Player ai, final SpellAbility sa) { final TargetRestrictions tgt = sa.getTargetRestrictions(); List list = CardUtil.getValidCardsToTarget(sa); diff --git a/forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java b/forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java index 212e4a7efe6..bec3a6643fd 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java @@ -48,4 +48,34 @@ public void testCounterBoonWrapperTargetsOwnCommander() { AssertJUnit.assertEquals("AI should not put a beneficial commander counter on an opponent's commander", aiCommander, forgeSa.getTargetCard()); } + + @Test + public void testForgeOfHeroesDoesNotTargetOpponentCommander() { + Game game = initAndCreateGame(); + Player ai = game.getPlayers().get(1); + Player opponent = game.getPlayers().get(0); + ai.setTeam(0); + opponent.setTeam(1); + + Card forge = addCard("Forge of Heroes", ai); + Card opponentCommander = addCard("Emrakul, the Aeons Torn", opponent); + opponentCommander.setCommander(true); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, ai); + game.getAction().checkStateEffects(true); + + SpellAbility forgeSa = findSAWithPrefix(forge, "{T}: Choose target commander"); + AssertJUnit.assertNotNull(forgeSa); + forgeSa.setActivatingPlayer(ai); + AssertJUnit.assertTrue("Opponent commander should be a legal Forge of Heroes target", + forgeSa.canTarget(opponentCommander)); + + SpellAbilityAi pumpAi = SpellApiToAi.Converter.get(ApiType.Pump); + AiPlayDecision decision = pumpAi.canPlayWithSubs(ai, forgeSa).decision(); + + AssertJUnit.assertFalse("AI should not activate Forge of Heroes only to benefit an opponent's commander", + AiPlayDecision.WillPlay.equals(decision)); + AssertJUnit.assertNull("AI should leave Forge of Heroes untargeted when only opponent commanders are valid", + forgeSa.getTargetCard()); + } } diff --git a/forge-gui/res/cardsfolder/f/forge_of_heroes.txt b/forge-gui/res/cardsfolder/f/forge_of_heroes.txt index dd9f3478349..2b0efcc6926 100644 --- a/forge-gui/res/cardsfolder/f/forge_of_heroes.txt +++ b/forge-gui/res/cardsfolder/f/forge_of_heroes.txt @@ -2,7 +2,7 @@ Name:Forge of Heroes ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ Pump | Cost$ T | ValidTgts$ Card.IsCommander+ThisTurnEntered | TgtPrompt$ Select target commander that entered this turn | SubAbility$ DBPutCounter | StackDescription$ SpellDescription | SpellDescription$ Choose target commander that entered this turn. Put a +1/+1 counter on it if it's a creature and a loyalty counter on it if it's a planeswalker. +A:AB$ Pump | Cost$ T | ValidTgts$ Card.IsCommander+ThisTurnEntered | AILogic$ AnyPhase | AITgts$ Card.IsCommander+ThisTurnEntered+YourTeamCtrl | TgtPrompt$ Select target commander that entered this turn | SubAbility$ DBPutCounter | StackDescription$ SpellDescription | SpellDescription$ Choose target commander that entered this turn. Put a +1/+1 counter on it if it's a creature and a loyalty counter on it if it's a planeswalker. SVar:DBPutCounter:DB$ PutCounter | Defined$ ParentTarget | CounterType$ P1P1 | CounterNum$ 1 | ConditionDefined$ ParentTarget | ConditionPresent$ Creature | ConditionCompare$ GE1 | SubAbility$ DBPutCounterCommander SVar:DBPutCounterCommander:DB$ PutCounter | Defined$ ParentTarget | CounterType$ LOYALTY | CounterNum$ 1 | ConditionDefined$ ParentTarget | ConditionPresent$ Planeswalker | ConditionCompare$ GE1 Oracle:{T}: Add {C}.\n{T}: Choose target commander that entered this turn. Put a +1/+1 counter on it if it's a creature and a loyalty counter on it if it's a planeswalker. From a73eed641716b11148448fd130f7abeff619d672 Mon Sep 17 00:00:00 2001 From: Madwand99 Date: Fri, 8 May 2026 21:12:13 -0700 Subject: [PATCH 3/4] Fix for unecessary repetition --- forge-gui/res/cardsfolder/f/forge_of_heroes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/f/forge_of_heroes.txt b/forge-gui/res/cardsfolder/f/forge_of_heroes.txt index 2b0efcc6926..739487d2daf 100644 --- a/forge-gui/res/cardsfolder/f/forge_of_heroes.txt +++ b/forge-gui/res/cardsfolder/f/forge_of_heroes.txt @@ -2,7 +2,7 @@ Name:Forge of Heroes ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ Pump | Cost$ T | ValidTgts$ Card.IsCommander+ThisTurnEntered | AILogic$ AnyPhase | AITgts$ Card.IsCommander+ThisTurnEntered+YourTeamCtrl | TgtPrompt$ Select target commander that entered this turn | SubAbility$ DBPutCounter | StackDescription$ SpellDescription | SpellDescription$ Choose target commander that entered this turn. Put a +1/+1 counter on it if it's a creature and a loyalty counter on it if it's a planeswalker. +A:AB$ Pump | Cost$ T | ValidTgts$ Card.IsCommander+ThisTurnEntered | AILogic$ AnyPhase | AITgts$ Card.YourTeamCtrl | TgtPrompt$ Select target commander that entered this turn | SubAbility$ DBPutCounter | StackDescription$ SpellDescription | SpellDescription$ Choose target commander that entered this turn. Put a +1/+1 counter on it if it's a creature and a loyalty counter on it if it's a planeswalker. SVar:DBPutCounter:DB$ PutCounter | Defined$ ParentTarget | CounterType$ P1P1 | CounterNum$ 1 | ConditionDefined$ ParentTarget | ConditionPresent$ Creature | ConditionCompare$ GE1 | SubAbility$ DBPutCounterCommander SVar:DBPutCounterCommander:DB$ PutCounter | Defined$ ParentTarget | CounterType$ LOYALTY | CounterNum$ 1 | ConditionDefined$ ParentTarget | ConditionPresent$ Planeswalker | ConditionCompare$ GE1 Oracle:{T}: Add {C}.\n{T}: Choose target commander that entered this turn. Put a +1/+1 counter on it if it's a creature and a loyalty counter on it if it's a planeswalker. From 15e2b76e848c8894df77a3e24cc10054fe721e7e Mon Sep 17 00:00:00 2001 From: Madwand99 Date: Sun, 10 May 2026 15:12:39 -0700 Subject: [PATCH 4/4] rmove tests --- .../java/forge/ai/ability/PumpAiTest.java | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java diff --git a/forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java b/forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java deleted file mode 100644 index bec3a6643fd..00000000000 --- a/forge-gui-desktop/src/test/java/forge/ai/ability/PumpAiTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package forge.ai.ability; - -import forge.ai.AITest; -import forge.ai.AiPlayDecision; -import forge.ai.SpellAbilityAi; -import forge.ai.SpellApiToAi; -import forge.game.Game; -import forge.game.ability.ApiType; -import forge.game.card.Card; -import forge.game.phase.PhaseType; -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; -import org.testng.AssertJUnit; -import org.testng.annotations.Test; - -public class PumpAiTest extends AITest { - @Test - public void testCounterBoonWrapperTargetsOwnCommander() { - Game game = initAndCreateGame(); - Player ai = game.getPlayers().get(1); - Player opponent = game.getPlayers().get(0); - ai.setTeam(0); - opponent.setTeam(1); - - Card forge = addCard("Forge of Heroes", ai); - Card aiCommander = addCard("Elvish Mystic", ai); - Card opponentCommander = addCard("Emrakul, the Aeons Torn", opponent); - aiCommander.setCommander(true); - opponentCommander.setCommander(true); - - game.getPhaseHandler().devModeSet(PhaseType.MAIN2, ai); - game.getAction().checkStateEffects(true); - - SpellAbility forgeSa = findSAWithPrefix(forge, "{T}: Choose target commander"); - AssertJUnit.assertNotNull(forgeSa); - forgeSa.setActivatingPlayer(ai); - AssertJUnit.assertNotNull("Forge of Heroes should have counter sub-abilities", forgeSa.getSubAbility()); - AssertJUnit.assertEquals("P1P1", forgeSa.getSubAbility().getParam("CounterType")); - AssertJUnit.assertEquals("ParentTarget", forgeSa.getSubAbility().getParam("Defined")); - AssertJUnit.assertTrue("AI commander should be a legal Forge of Heroes target", forgeSa.canTarget(aiCommander)); - AssertJUnit.assertTrue("Opponent commander should be a legal Forge of Heroes target", forgeSa.canTarget(opponentCommander)); - - SpellAbilityAi pumpAi = SpellApiToAi.Converter.get(ApiType.Pump); - AiPlayDecision decision = pumpAi.canPlayWithSubs(ai, forgeSa).decision(); - - AssertJUnit.assertEquals("AI should activate Forge of Heroes for its own commander", - AiPlayDecision.WillPlay, decision); - AssertJUnit.assertEquals("AI should not put a beneficial commander counter on an opponent's commander", - aiCommander, forgeSa.getTargetCard()); - } - - @Test - public void testForgeOfHeroesDoesNotTargetOpponentCommander() { - Game game = initAndCreateGame(); - Player ai = game.getPlayers().get(1); - Player opponent = game.getPlayers().get(0); - ai.setTeam(0); - opponent.setTeam(1); - - Card forge = addCard("Forge of Heroes", ai); - Card opponentCommander = addCard("Emrakul, the Aeons Torn", opponent); - opponentCommander.setCommander(true); - - game.getPhaseHandler().devModeSet(PhaseType.MAIN2, ai); - game.getAction().checkStateEffects(true); - - SpellAbility forgeSa = findSAWithPrefix(forge, "{T}: Choose target commander"); - AssertJUnit.assertNotNull(forgeSa); - forgeSa.setActivatingPlayer(ai); - AssertJUnit.assertTrue("Opponent commander should be a legal Forge of Heroes target", - forgeSa.canTarget(opponentCommander)); - - SpellAbilityAi pumpAi = SpellApiToAi.Converter.get(ApiType.Pump); - AiPlayDecision decision = pumpAi.canPlayWithSubs(ai, forgeSa).decision(); - - AssertJUnit.assertFalse("AI should not activate Forge of Heroes only to benefit an opponent's commander", - AiPlayDecision.WillPlay.equals(decision)); - AssertJUnit.assertNull("AI should leave Forge of Heroes untargeted when only opponent commanders are valid", - forgeSa.getTargetCard()); - } -}