From 32ce1d85e9d845e55cf09fe78df788f066e983d6 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 29 May 2020 14:41:24 +0200 Subject: [PATCH] * Fix of some problems of zone change related triggered abilities that had not been correctly implemented (fixes #6586). --- .../mage/cards/a/AnaxHardenedInTheForge.java | 56 +++++++------- Mage.Sets/src/mage/cards/p/PawnOfUlamog.java | 75 +++++-------------- .../other/SakashimaTheImpostorTest.java | 45 +++++++++++ .../mage/abilities/TriggeredAbilityImpl.java | 17 +++-- .../common/ZoneChangeAllTriggeredAbility.java | 3 + 5 files changed, 102 insertions(+), 94 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AnaxHardenedInTheForge.java b/Mage.Sets/src/mage/cards/a/AnaxHardenedInTheForge.java index 2412c0a40fb..b55489b2c7d 100644 --- a/Mage.Sets/src/mage/cards/a/AnaxHardenedInTheForge.java +++ b/Mage.Sets/src/mage/cards/a/AnaxHardenedInTheForge.java @@ -1,7 +1,6 @@ package mage.cards.a; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.DevotionCount; import mage.abilities.effects.common.CreateTokenEffect; @@ -12,17 +11,27 @@ import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.PermanentToken; import mage.game.permanent.token.SatyrCantBlockToken; -import java.util.Objects; import java.util.UUID; +import mage.abilities.common.DiesThisOrAnotherCreatureTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.TokenPredicate; /** * @author TheElk801 */ public final class AnaxHardenedInTheForge extends CardImpl { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nontoken creature you control"); + + static { + filter.add(TargetController.YOU.getControllerPredicate()); + filter.add(Predicates.not(TokenPredicate.instance)); + } + public AnaxHardenedInTheForge(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{1}{R}{R}"); @@ -38,8 +47,9 @@ public final class AnaxHardenedInTheForge extends CardImpl { .setText("{this}'s power is equal to your devotion to red") ).addHint(DevotionCount.R.getHint())); - // Whenever Anax or another nontoken creature you control dies, create a 1/1 red Satyr creature token with "This creature can't block." If the creature had power 4 or greater, create two of those tokens instead. - this.addAbility(new AnaxHardenedInTheForgeTriggeredAbility()); + // Whenever Anax or another nontoken creature you control dies, create a 1/1 red Satyr creature token + // with "This creature can't block." If the creature had power 4 or greater, create two of those tokens instead. + this.addAbility(new AnaxHardenedInTheForgeTriggeredAbility(null, false, filter)); } private AnaxHardenedInTheForge(final AnaxHardenedInTheForge card) { @@ -52,10 +62,10 @@ public final class AnaxHardenedInTheForge extends CardImpl { } } -class AnaxHardenedInTheForgeTriggeredAbility extends TriggeredAbilityImpl { +class AnaxHardenedInTheForgeTriggeredAbility extends DiesThisOrAnotherCreatureTriggeredAbility { - AnaxHardenedInTheForgeTriggeredAbility() { - super(Zone.BATTLEFIELD, null, false); + AnaxHardenedInTheForgeTriggeredAbility(Effect effect, boolean optional, FilterCreaturePermanent filter) { + super(effect, optional, filter); } private AnaxHardenedInTheForgeTriggeredAbility(final AnaxHardenedInTheForgeTriggeredAbility ability) { @@ -67,33 +77,21 @@ class AnaxHardenedInTheForgeTriggeredAbility extends TriggeredAbilityImpl { return new AnaxHardenedInTheForgeTriggeredAbility(this); } - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - @Override public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (!zEvent.isDiesEvent()) { - return false; + if (super.checkTrigger(event, game)) { + int tokenCount = ((ZoneChangeEvent) event).getTarget().getPower().getValue() > 3 ? 2 : 1; + this.getEffects().clear(); + this.addEffect(new CreateTokenEffect(new SatyrCantBlockToken(), tokenCount)); + return true; } - if (!zEvent.getTarget().getId().equals(getSourceId()) - && (zEvent.getTarget() instanceof PermanentToken - || !zEvent.getTarget().isCreature() - || !Objects.equals(zEvent.getTarget().getControllerId(), getControllerId()))) { - return false; - } - int tokenCount = zEvent.getTarget().getPower().getValue() > 3 ? 2 : 1; - this.getEffects().clear(); - this.addEffect(new CreateTokenEffect(new SatyrCantBlockToken(), tokenCount)); - return true; + return false; } @Override public String getRule() { - return "Whenever {this} or another nontoken creature you control dies, " + - "create a 1/1 red Satyr creature token with \"This creature can't block.\" " + - "If the creature had power 4 or greater, create two of those tokens instead."; + return "Whenever {this} or another nontoken creature you control dies, " + + "create a 1/1 red Satyr creature token with \"This creature can't block.\" " + + "If the creature had power 4 or greater, create two of those tokens instead."; } } diff --git a/Mage.Sets/src/mage/cards/p/PawnOfUlamog.java b/Mage.Sets/src/mage/cards/p/PawnOfUlamog.java index d9aa4601a38..222b1b8f453 100644 --- a/Mage.Sets/src/mage/cards/p/PawnOfUlamog.java +++ b/Mage.Sets/src/mage/cards/p/PawnOfUlamog.java @@ -1,22 +1,17 @@ - package mage.cards.p; import java.util.UUID; import mage.MageInt; -import mage.MageObject; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DiesThisOrAnotherCreatureTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.TokenPredicate; import mage.game.permanent.token.EldraziSpawnToken; /** @@ -24,16 +19,25 @@ import mage.game.permanent.token.EldraziSpawnToken; * @author North */ public final class PawnOfUlamog extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nontoken creature you control"); + static { + filter.add(TargetController.YOU.getControllerPredicate()); + filter.add(Predicates.not(TokenPredicate.instance)); + } + public PawnOfUlamog(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{1}{B}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{B}"); this.subtype.add(SubType.VAMPIRE); this.subtype.add(SubType.SHAMAN); this.power = new MageInt(2); this.toughness = new MageInt(2); - this.addAbility(new PawnOfUlamogTriggeredAbility()); + // Whenever Pawn of Ulamog or another nontoken creature you control dies, you may create a 0/1 colorless + // Eldrazi Spawn creature token. It has "Sacrifice this creature: Add {C}." + this.addAbility(new DiesThisOrAnotherCreatureTriggeredAbility(new CreateTokenEffect(new EldraziSpawnToken()), true, filter)); } public PawnOfUlamog(final PawnOfUlamog card) { @@ -44,49 +48,4 @@ public final class PawnOfUlamog extends CardImpl { public PawnOfUlamog copy() { return new PawnOfUlamog(this); } -} - -class PawnOfUlamogTriggeredAbility extends TriggeredAbilityImpl { - - public PawnOfUlamogTriggeredAbility() { - super(Zone.BATTLEFIELD, new CreateTokenEffect(new EldraziSpawnToken()), true); - } - - public PawnOfUlamogTriggeredAbility(PawnOfUlamogTriggeredAbility ability) { - super(ability); - } - - @Override - public PawnOfUlamogTriggeredAbility copy() { - return new PawnOfUlamogTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == EventType.ZONE_CHANGE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - UUID targetId = event.getTargetId(); - MageObject card = game.getLastKnownInformation(targetId, Zone.BATTLEFIELD); - if (card instanceof Permanent && !(card instanceof PermanentToken)) { - Permanent permanent = (Permanent) card; - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.isDiesEvent() - && permanent.isControlledBy(this.controllerId) - && (targetId.equals(this.getSourceId()) - || (permanent.isCreature() - && !targetId.equals(this.getSourceId()) - && !(permanent instanceof PermanentToken)))) { - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever Pawn of Ulamog or another nontoken creature you control dies, you may create a 0/1 colorless Eldrazi Spawn creature token. It has \"Sacrifice this creature: Add {C}.\""; - } -} +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SakashimaTheImpostorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SakashimaTheImpostorTest.java index 267229cccdc..b6cbba80e8a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SakashimaTheImpostorTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/other/SakashimaTheImpostorTest.java @@ -9,10 +9,17 @@ import org.mage.test.serverside.base.CardTestPlayerBase; * Created by alexsandro on 06/03/17. */ public class SakashimaTheImpostorTest extends CardTestPlayerBase { + @Test public void copySpellStutterTest() { + // Flash, Flying + // When Spellstutter Sprite enters the battlefield, counter target spell with converted mana cost X or less, + // where X is the number of Faeries you control. addCard(Zone.BATTLEFIELD, playerA, "Spellstutter Sprite", 1); addCard(Zone.BATTLEFIELD, playerB, "Island", 4); + // You may have Sakashima the Impostor enter the battlefield as a copy of any creature on the battlefield, + // except its name is Sakashima the Impostor, it's legendary in addition to its other types, + // and it has "{2}{U}{U}: Return Sakashima the Impostor to its owner's hand at the beginning of the next end step." addCard(Zone.HAND, playerB, "Sakashima the Impostor", 4); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Sakashima the Impostor"); @@ -25,4 +32,42 @@ public class SakashimaTheImpostorTest extends CardTestPlayerBase { assertPowerToughness(playerB, "Sakashima the Impostor", 1, 1); } + + /** + * I played Sakashima the Imposter copying an opponents Pawn of Ulamaog. + * Sakashima gained the following ability: "Whenever Pawn of Ulamog or + * another nontoken creature you control dies, you may create a 0/1 + * colorless Eldrazi Spawn creature token. It has "Sacrifice this creature: + * Add {C}." Then Sakashima died due to combat damage and the ability did + * not trigger. + * + */ + @Test + public void copyDiesTriggeredTest() { + // Whenever Pawn of Ulamog or another nontoken creature you control dies, you may create a 0/1 colorless + // Eldrazi Spawn creature token. It has "Sacrifice this creature: Add {C}." + addCard(Zone.BATTLEFIELD, playerA, "Pawn of Ulamog", 1); // Creature 2/2 + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // Creature 2/2 + + addCard(Zone.BATTLEFIELD, playerB, "Island", 4); + // You may have Sakashima the Impostor enter the battlefield as a copy of any creature on the battlefield, + // except its name is Sakashima the Impostor, it's legendary in addition to its other types, + // and it has "{2}{U}{U}: Return Sakashima the Impostor to its owner's hand at the beginning of the next end step." + addCard(Zone.HAND, playerB, "Sakashima the Impostor", 4); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Sakashima the Impostor"); + setChoice(playerB, "Pawn of Ulamog"); + + attack(4, playerB, "Sakashima the Impostor"); + block(4, playerA, "Silvercoat Lion", "Sakashima the Impostor"); + + setStopAt(4, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, "Silvercoat Lion", 1); + assertGraveyardCount(playerB, "Sakashima the Impostor", 1); + + assertPermanentCount(playerA, "Eldrazi Spawn", 1); + assertPermanentCount(playerB, "Eldrazi Spawn", 1); + } } diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index e0374619982..5542547c16e 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -171,21 +171,24 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } } } + if (isLeavesTheBattlefieldTrigger()) { + source = zce.getTarget(); + } + break; case DESTROYED_PERMANENT: if (isLeavesTheBattlefieldTrigger()) { - if (event.getType() == EventType.DESTROYED_PERMANENT) { - source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); - } else if (((ZoneChangeEvent) event).getTarget() != null) { - source = ((ZoneChangeEvent) event).getTarget(); - } else { - source = game.getLastKnownInformation(getSourceId(), event.getZone()); - } + source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); } + break; case PHASED_OUT: case PHASED_IN: + if (isLeavesTheBattlefieldTrigger()) { + source = game.getLastKnownInformation(getSourceId(), event.getZone()); + } if (this.zone == Zone.ALL || game.getLastKnownInformation(getSourceId(), zone) != null) { return this.hasSourceObjectAbility(game, source, event); } + break; } return super.isInUseableZone(game, source, event); } diff --git a/Mage/src/main/java/mage/abilities/common/ZoneChangeAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ZoneChangeAllTriggeredAbility.java index 9d97496682c..10ee16f5384 100644 --- a/Mage/src/main/java/mage/abilities/common/ZoneChangeAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ZoneChangeAllTriggeredAbility.java @@ -27,6 +27,9 @@ public class ZoneChangeAllTriggeredAbility extends TriggeredAbilityImpl { public ZoneChangeAllTriggeredAbility(Zone zone, Zone fromZone, Zone toZone, Effect effect, FilterPermanent filter, String rule, boolean optional) { super(zone, effect, optional); + if (fromZone == Zone.BATTLEFIELD) { + setLeavesTheBattlefieldTrigger(true); + } this.fromZone = fromZone; this.toZone = toZone; this.rule = rule;