From 0b4dbaabba670096d6abbf094b415bff36ebb705 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 23 Apr 2025 05:38:14 +0400 Subject: [PATCH] Ketramose, the New Dawn - fixed that it doesn't trigger on itself (miss setLeavesTheBattlefieldTrigger, related to eafbd5a95c32fa7011d918e5bf7fb49c7833ffec) --- .../src/mage/cards/k/KetramoseTheNewDawn.java | 36 +++++++++-------- .../src/mage/cards/r/RelicOfProgenitus.java | 4 +- .../single/dft/KetramoseTheNewDawnTest.java | 40 ++++++++++++++----- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/Mage.Sets/src/mage/cards/k/KetramoseTheNewDawn.java b/Mage.Sets/src/mage/cards/k/KetramoseTheNewDawn.java index cfcfc15c3ce..dedc14b5bc6 100644 --- a/Mage.Sets/src/mage/cards/k/KetramoseTheNewDawn.java +++ b/Mage.Sets/src/mage/cards/k/KetramoseTheNewDawn.java @@ -13,21 +13,14 @@ import mage.abilities.effects.common.combat.CantAttackBlockUnlessConditionSource import mage.abilities.keyword.IndestructibleAbility; import mage.abilities.keyword.LifelinkAbility; import mage.abilities.keyword.MenaceAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.ComparisonType; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeBatchEvent; import mage.game.events.ZoneChangeEvent; -import mage.game.events.ZoneChangeGroupEvent; -import java.util.Objects; import java.util.UUID; /** @@ -54,7 +47,7 @@ public final class KetramoseTheNewDawn extends CardImpl { // Ketramose can't attack or block unless there are seven or more cards in exile. this.addAbility(new SimpleStaticAbility( - new CantAttackBlockUnlessConditionSourceEffect(new CardsInExileCondition(ComparisonType.OR_GREATER, 7)) + new CantAttackBlockUnlessConditionSourceEffect(new CardsInExileCondition(ComparisonType.OR_GREATER, 7)) ).addHint(CardsInExileCount.ALL.getHint())); // Whenever one or more cards are put into exile from graveyards and/or the battlefield during your turn, you draw a card and lose 1 life. @@ -72,10 +65,13 @@ public final class KetramoseTheNewDawn extends CardImpl { } } -class KetramoseTriggeredAbility extends TriggeredAbilityImpl { + +class KetramoseTriggeredAbility extends TriggeredAbilityImpl implements BatchTriggeredAbility { + KetramoseTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), false); this.addEffect(new LoseLifeSourceControllerEffect(1)); + setLeavesTheBattlefieldTrigger(true); } private KetramoseTriggeredAbility(final KetramoseTriggeredAbility ability) { @@ -84,25 +80,31 @@ class KetramoseTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE_GROUP; + return event.getType() == GameEvent.EventType.ZONE_CHANGE_BATCH; + } + + @Override + public boolean checkEvent(ZoneChangeEvent event, Game game) { + if (event.getToZone() != Zone.EXILED) { + return false; + } + return event.getFromZone() == Zone.GRAVEYARD || event.getFromZone() == Zone.BATTLEFIELD; } @Override public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; return game.getActivePlayerId().equals(getControllerId()) - && zEvent.getFromZone() == Zone.GRAVEYARD || zEvent.getFromZone() == Zone.BATTLEFIELD; + && !getFilteredEvents((ZoneChangeBatchEvent) event, game).isEmpty(); } @Override - public TriggeredAbility copy() - { + public TriggeredAbility copy() { return new KetramoseTriggeredAbility(this); } @Override public String getRule() { return "Whenever one or more cards are put into exile from graveyards" - + " and/or the battlefield during your turn, you draw a card and lose 1 life."; + + " and/or the battlefield during your turn, you draw a card and lose 1 life."; } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/r/RelicOfProgenitus.java b/Mage.Sets/src/mage/cards/r/RelicOfProgenitus.java index a0dae504ccf..e179c95ab2f 100644 --- a/Mage.Sets/src/mage/cards/r/RelicOfProgenitus.java +++ b/Mage.Sets/src/mage/cards/r/RelicOfProgenitus.java @@ -1,4 +1,3 @@ - package mage.cards.r; import java.util.UUID; @@ -32,10 +31,11 @@ public final class RelicOfProgenitus extends CardImpl { public RelicOfProgenitus(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); - // {tap}: Target player exiles a card from their graveyard. + // {T}: Target player exiles a card from their graveyard. Ability firstAbility = new SimpleActivatedAbility(new RelicOfProgenitusEffect(), new TapSourceCost()); firstAbility.addTarget(new TargetPlayer()); this.addAbility(firstAbility); + // {1}, Exile Relic of Progenitus: Exile all cards from all graveyards. Draw a card. Ability secondAbility = new SimpleActivatedAbility(new ExileGraveyardAllPlayersEffect(), new GenericManaCost(1)); secondAbility.addCost(new ExileSourceCost()); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/KetramoseTheNewDawnTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/KetramoseTheNewDawnTest.java index 0b2f9e09be3..452f9a80bf7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/KetramoseTheNewDawnTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dft/KetramoseTheNewDawnTest.java @@ -5,17 +5,27 @@ import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; -import static org.junit.jupiter.api.Assertions.*; - public class KetramoseTheNewDawnTest extends CardTestPlayerBase { + /** + * Whenever one or more cards are put into exile from graveyards and/or the battlefield + * during your turn, you draw a card and lose 1 life. + */ private final String ketramose = "Ketramose, the New Dawn"; + /** + * {T}: Target player exiles a card from their graveyard. + * {1}, Exile Relic of Progenitus: Exile all cards from all graveyards. Draw a card. + */ private final String relic = "Relic of Progenitus"; + /** + * Exile target creature you control, then return it to the battlefield under its owner's control. + */ private final String ephemerate = "Ephemerate"; @Test public void testExile() { setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, ketramose); addCard(Zone.BATTLEFIELD, playerA, relic); addCard(Zone.HAND, playerA, ephemerate); @@ -23,17 +33,29 @@ public class KetramoseTheNewDawnTest extends CardTestPlayerBase { addCard(Zone.GRAVEYARD, playerA, "Forest", 10); addCard(Zone.GRAVEYARD, playerB, "Forest", 10); - activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}:"); + // exile single + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player"); addTarget(playerA, playerB); addTarget(playerB, "Forest"); - activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{1}, Exile {this}"); - waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, ephemerate, ketramose, true); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkLife("exile single - after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20 - 1); + + // must have two triggers: exile on cost and exile on resolve + // exile on cost + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, Exile {this}"); + checkStackSize("exile on cost - ability + trigger on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); + checkLife("exile on cost - after trigger", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20 - 1 - 1); + // exile on resolve + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkLife("exile on resolve - after trigger", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20 - 1 - 1 - 1); + + // exile ketramose itself and return + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, ephemerate, ketramose); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkLife("exile itself - must trigger", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 20 - 1 - 1 - 1 - 1); setStopAt(1, PhaseStep.END_TURN); execute(); - - assertHandCount(playerA, 4); - assertLife(playerA, 20 - 3); } } \ No newline at end of file