diff --git a/forge-game/src/main/java/forge/game/card/CardZoneTable.java b/forge-game/src/main/java/forge/game/card/CardZoneTable.java index fbec5fa601e..720d5d5737b 100644 --- a/forge-game/src/main/java/forge/game/card/CardZoneTable.java +++ b/forge-game/src/main/java/forge/game/card/CardZoneTable.java @@ -11,6 +11,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Table; import forge.game.Game; +import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; @@ -45,4 +46,39 @@ public class CardZoneTable extends ForwardingTable origin, ZoneType destination, String valid, Card host, SpellAbility sa) { + CardCollection allCards = new CardCollection(); + if (destination != null) { + if (!containsColumn(destination)) { + return allCards; + } + } + if (origin != null) { + for (ZoneType z : origin) { + if (containsRow(z)) { + if (destination != null) { + allCards.addAll(row(z).get(destination)); + } else { + for (CardCollection c : row(z).values()) { + allCards.addAll(c); + } + } + } + } + } else if (destination != null) { + for (CardCollection c : column(destination).values()) { + allCards.addAll(c); + } + } else { + for (CardCollection c : values()) { + allCards.addAll(c); + } + } + + if (valid != null) { + allCards = CardLists.getValidCards(allCards, valid.split(","), host.getController(), host, sa); + } + return allCards; + } } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java index 3651c3d9ae7..0993e8dae29 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZoneAll.java @@ -42,48 +42,19 @@ public class TriggerChangesZoneAll extends Trigger { } private CardCollection filterCards(CardZoneTable table) { - CardCollection allCards = new CardCollection(); ZoneType destination = null; + List origin = null; - if (hasParam("Destination")) { - if (!getParam("Destination").equals("Any")) { - destination = ZoneType.valueOf(getParam("Destination")); - if (!table.containsColumn(destination)) { - return allCards; - } - } + if (hasParam("Destination") && !getParam("Destination").equals("Any")) { + destination = ZoneType.valueOf(getParam("Destination")); } if (hasParam("Origin") && !getParam("Origin").equals("Any")) { - if (getParam("Origin") == null) { - return allCards; - } - final List origin = ZoneType.listValueOf(getParam("Origin")); - for (ZoneType z : origin) { - if (table.containsRow(z)) { - if (destination != null) { - allCards.addAll(table.row(z).get(destination)); - } else { - for (CardCollection c : table.row(z).values()) { - allCards.addAll(c); - } - } - } - } - } else if (destination != null) { - for (CardCollection c : table.column(destination).values()) { - allCards.addAll(c); - } - } else { - for (CardCollection c : table.values()) { - allCards.addAll(c); - } + origin = ZoneType.listValueOf(getParam("Origin")); } - if (hasParam("ValidCards")) { - allCards = CardLists.getValidCards(allCards, getParam("ValidCards").split(","), - getHostCard().getController(), getHostCard(), null); - } - return allCards; + final String valid = this.getParamOrDefault("ValidCards", null); + + return table.filterCards(origin, destination, valid, getHostCard(), null); } } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 31cfb4a1612..308b373411d 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -27,6 +27,7 @@ import forge.game.ability.effects.CharmEffect; import forge.game.card.Card; import forge.game.card.CardLists; import forge.game.card.CardUtil; +import forge.game.card.CardZoneTable; import forge.game.keyword.KeywordInterface; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -44,6 +45,7 @@ import io.sentry.event.BreadcrumbBuilder; import java.util.*; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -693,13 +695,14 @@ public class TriggerHandler { private int handlePanharmonicon(final Trigger t, final Map runParams, final Player p) { Card host = t.getHostCard(); - // not a changesZone trigger - if (t.getMode() != TriggerType.ChangesZone) { + // not a changesZone trigger or changesZoneAll + if (t.getMode() != TriggerType.ChangesZone && t.getMode() != TriggerType.ChangesZoneAll) { return 0; } // leave battlefield trigger, might be dying - if ("Battlefield".equals(t.getParam("Origin"))) { + // only real changeszone look back for this + if (t.getMode() == TriggerType.ChangesZone && "Battlefield".equals(t.getParam("Origin"))) { // Need to get the last info from the trigger host host = game.getChangeZoneLKIInfo(host); } @@ -710,30 +713,17 @@ public class TriggerHandler { } int n = 0; - // iterate over all cards - final List lastCards = CardLists.filterControlledBy(p.getGame().getLastStateBattlefield(), p); - for (final Card ck : lastCards) { - for (final KeywordInterface ki : ck.getKeywords()) { - final String kw = ki.getOriginal(); - if (kw.startsWith("Panharmonicon")) { - // Enter the Battlefield Trigger - if (runParams.get("Destination") instanceof String) { - final String dest = (String) runParams.get("Destination"); - if ("Battlefield".equals(dest) && runParams.get("Card") instanceof Card) { - final Card card = (Card) runParams.get("Card"); - final String valid = kw.split(":")[1]; - if (card.isValid(valid.split(","), p, ck, null)) { - n++; - } - } - } - } else if (kw.startsWith("Dieharmonicon")) { - // 700.4. The term dies means “is put into a graveyard from the battlefield.” - if (runParams.get("Origin") instanceof String) { - final String origin = (String) runParams.get("Origin"); - if ("Battlefield".equals(origin) && runParams.get("Destination") instanceof String) { + if (t.getMode() == TriggerType.ChangesZone) { + // iterate over all cards + final List lastCards = CardLists.filterControlledBy(p.getGame().getLastStateBattlefield(), p); + for (final Card ck : lastCards) { + for (final KeywordInterface ki : ck.getKeywords()) { + final String kw = ki.getOriginal(); + if (kw.startsWith("Panharmonicon")) { + // Enter the Battlefield Trigger + if (runParams.get("Destination") instanceof String) { final String dest = (String) runParams.get("Destination"); - if ("Graveyard".equals(dest) && runParams.get("Card") instanceof Card) { + if ("Battlefield".equals(dest) && runParams.get("Card") instanceof Card) { final Card card = (Card) runParams.get("Card"); final String valid = kw.split(":")[1]; if (card.isValid(valid.split(","), p, ck, null)) { @@ -741,6 +731,43 @@ public class TriggerHandler { } } } + } else if (kw.startsWith("Dieharmonicon")) { + // 700.4. The term dies means “is put into a graveyard from the battlefield.” + if (runParams.get("Origin") instanceof String) { + final String origin = (String) runParams.get("Origin"); + if ("Battlefield".equals(origin) && runParams.get("Destination") instanceof String) { + final String dest = (String) runParams.get("Destination"); + if ("Graveyard".equals(dest) && runParams.get("Card") instanceof Card) { + final Card card = (Card) runParams.get("Card"); + final String valid = kw.split(":")[1]; + if (card.isValid(valid.split(","), p, ck, null)) { + n++; + } + } + } + } + } + } + } + } else if (t.getMode() == TriggerType.ChangesZoneAll) { + final CardZoneTable table = (CardZoneTable) runParams.get("Cards"); + // iterate over all cards + for (final Card ck : p.getCardsIn(ZoneType.Battlefield)) { + for (final KeywordInterface ki : ck.getKeywords()) { + final String kw = ki.getOriginal(); + if (kw.startsWith("Panharmonicon")) { + // currently there is no ChangesZoneAll that would trigger on etb + final String valid = kw.split(":")[1]; + if (!table.filterCards(null, ZoneType.Battlefield, valid, ck, null).isEmpty()) { + n++; + } + } else if (kw.startsWith("Dieharmonicon")) { + // 700.4. The term dies means “is put into a graveyard from the battlefield.” + final String valid = kw.split(":")[1]; + if (!table.filterCards(ImmutableList.of(ZoneType.Battlefield), ZoneType.Graveyard, + valid, ck, null).isEmpty()) { + n++; + } } } } diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java index 5493b7da670..0d15d62f5f4 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java @@ -1589,4 +1589,111 @@ public class GameSimulatorTest extends SimulationTestCase { int numZombies = countCardsWithName(simGame, "Zombie"); assertTrue(numZombies == 3); } + + + public void testTeysaKarlovGitrogMonster() { + + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(0); + game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p); + + addCard("Teysa Karlov", p); + addCard("The Gitrog Monster", p); + addCard("Dryad Arbor", p); + + for (int i = 0; i < 4; i++) { + addCard("Plains", p); + addCardToZone("Plains", p, ZoneType.Library); + } + + Card armageddon = addCardToZone("Armageddon", p, ZoneType.Hand); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + + SpellAbility armageddonSA = armageddon.getFirstSpellAbility(); + + GameSimulator sim = createSimulator(game, p); + int score = sim.simulateSpellAbility(armageddonSA).value; + assertTrue(score > 0); + Game simGame = sim.getSimulatedGameState(); + + // Two cards drawn + assertTrue(simGame.getPlayers().get(0).getZone(ZoneType.Hand).size() == 2); + } + + public void testTeysaKarlovGitrogMonsterGitrogDies() { + + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(0); + game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p); + + Card teysa = addCard("Teysa Karlov", p); + addCard("The Gitrog Monster", p); + addCard("Dryad Arbor", p); + + String indestructibilityName = "Indestructibility"; + Card indestructibility = addCard(indestructibilityName, p); + + indestructibility.attachToEntity(teysa); + + // update Indestructible state + game.getAction().checkStateEffects(true); + + for (int i = 0; i < 4; i++) { + addCard("Plains", p); + addCardToZone("Plains", p, ZoneType.Library); + } + + Card armageddon = addCardToZone("Wrath of God", p, ZoneType.Hand); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + + SpellAbility armageddonSA = armageddon.getFirstSpellAbility(); + + GameSimulator sim = createSimulator(game, p); + int score = sim.simulateSpellAbility(armageddonSA).value; + assertTrue(score > 0); + Game simGame = sim.getSimulatedGameState(); + + // One cards drawn + assertTrue(simGame.getPlayers().get(0).getZone(ZoneType.Hand).size() == 1); + } + + public void testTeysaKarlovGitrogMonsterTeysaDies() { + + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(0); + game.getPhaseHandler().devModeSet(PhaseType.MAIN1, p); + + addCard("Teysa Karlov", p); + Card gitrog = addCard("The Gitrog Monster", p); + addCard("Dryad Arbor", p); + + String indestructibilityName = "Indestructibility"; + Card indestructibility = addCard(indestructibilityName, p); + + indestructibility.attachToEntity(gitrog); + + // update Indestructible state + game.getAction().checkStateEffects(true); + + for (int i = 0; i < 4; i++) { + addCard("Plains", p); + addCardToZone("Plains", p, ZoneType.Library); + } + + Card armageddon = addCardToZone("Wrath of God", p, ZoneType.Hand); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + + SpellAbility armageddonSA = armageddon.getFirstSpellAbility(); + + GameSimulator sim = createSimulator(game, p); + int score = sim.simulateSpellAbility(armageddonSA).value; + assertTrue(score > 0); + Game simGame = sim.getSimulatedGameState(); + + // One cards drawn + assertTrue(simGame.getPlayers().get(0).getZone(ZoneType.Hand).size() == 1); + } }