diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 7921f9b61600..998420cb2b4b 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -158,7 +158,7 @@ public enum SpellApiToAi { .put(ApiType.Regenerate, RegenerateAi.class) .put(ApiType.Regeneration, AlwaysPlayAi.class) .put(ApiType.RemoveCounter, CountersRemoveAi.class) - .put(ApiType.RemoveCounterAll, CannotPlayAi.class) + .put(ApiType.RemoveCounterAll, CountersRemoveAllAi.class) .put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class) .put(ApiType.RemoveFromGame, AlwaysPlayAi.class) .put(ApiType.RemoveFromMatch, AlwaysPlayAi.class) diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAllAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAllAi.java new file mode 100644 index 000000000000..38eaf7aa7f79 --- /dev/null +++ b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAllAi.java @@ -0,0 +1,26 @@ +package forge.ai.ability; + +import forge.ai.AiAbilityDecision; +import forge.ai.AiPlayDecision; +import forge.ai.SpellAbilityAi; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; + +/** + * The AI has no logic for choosing to remove all counters of a kind, so it still will not activate + * one on purpose. As a sub-ability it is a consequence of an effect the AI has already decided it + * wants - usually bookkeeping, like Oblivion Stone clearing the fate counters it just checked - so + * it must not veto its own parent. + */ +public class CountersRemoveAllAi extends SpellAbilityAi { + + @Override + protected AiAbilityDecision canPlay(Player aiPlayer, SpellAbility sa) { + return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); + } + + @Override + public AiAbilityDecision chkDrawback(Player aiPlayer, SpellAbility sa) { + return new AiAbilityDecision(100, AiPlayDecision.WillPlay); + } +} diff --git a/forge-gui-desktop/src/test/java/forge/ai/ability/RemoveCounterAllDrawbackTest.java b/forge-gui-desktop/src/test/java/forge/ai/ability/RemoveCounterAllDrawbackTest.java new file mode 100644 index 000000000000..3c4f5e03f54a --- /dev/null +++ b/forge-gui-desktop/src/test/java/forge/ai/ability/RemoveCounterAllDrawbackTest.java @@ -0,0 +1,47 @@ +package forge.ai.ability; + +import forge.ai.AITest; +import forge.game.Game; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.zone.ZoneType; +import org.testng.annotations.Test; + +import static junit.framework.Assert.assertEquals; + +/** + * RemoveCounterAll has no AI, so as a sub-ability it used to refuse and take its parent down with + * it. Oblivion Stone's wipe ends by clearing the fate counters it just checked, which meant the + * wipe itself could never be activated. + */ +public class RemoveCounterAllDrawbackTest extends AITest { + + @Test + public void bookkeepingSubAbilityDoesNotVetoTheWipe() { + Game game = initAndCreateGame(); + Player ai = game.getPlayers().get(1); + Player opp = game.getPlayers().get(0); + ai.setTeam(0); + opp.setTeam(1); + + for (int i = 0; i < 8; i++) { + addCard("Wastes", ai); + } + fillLibrary(ai, 15); + fillLibrary(opp, 15); + // nothing of the AI's own is in range, so the wipe is pure upside + addCard("Grizzly Bears", opp); + addCard("Grizzly Bears", opp); + addCard("Grizzly Bears", opp); + addCard("Oblivion Stone", ai); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, ai); + game.getAction().checkStateEffects(true); + playUntilNextTurn(game); + + assertEquals("it sacrificed Oblivion Stone to wipe", 1, + countCardsWithName(game, "Oblivion Stone", ZoneType.Graveyard)); + assertEquals("the opponent's board is gone", 0, + countCardsWithName(game, "Grizzly Bears", ZoneType.Battlefield)); + } +} diff --git a/forge-gui/res/cardsfolder/o/oblivion_stone.txt b/forge-gui/res/cardsfolder/o/oblivion_stone.txt index 49920c056fcc..33b49ee0901e 100644 --- a/forge-gui/res/cardsfolder/o/oblivion_stone.txt +++ b/forge-gui/res/cardsfolder/o/oblivion_stone.txt @@ -1,8 +1,7 @@ Name:Oblivion Stone ManaCost:3 Types:Artifact -A:AB$ PutCounter | Cost$ 4 T | ValidTgts$ Permanent | CounterType$ FATE | CounterNum$ 1 | SpellDescription$ Put a fate counter on target permanent. +A:AB$ PutCounter | Cost$ 4 T | ValidTgts$ Permanent | CounterType$ FATE | CounterNum$ 1 | AILogic$ AtOppEOT | AITgts$ Permanent.YouCtrl+Other+nonLand | SpellDescription$ Put a fate counter on target permanent. A:AB$ DestroyAll | Cost$ 5 T Sac<1/CARDNAME> | ValidCards$ Permanent.nonLand+counters_LT1_FATE | SubAbility$ DBRemove | SpellDescription$ Destroy each nonland permanent without a fate counter on it, then remove all fate counters from all permanents. SVar:DBRemove:DB$ RemoveCounterAll | ValidCards$ Permanent | CounterType$ FATE | AllCounters$ True -AI:RemoveDeck:All Oracle:{4}, {T}: Put a fate counter on target permanent.\n{5}, {T}, Sacrifice Oblivion Stone: Destroy each nonland permanent without a fate counter on it, then remove all fate counters from all permanents.