* Fix of some problems of zone change related triggered abilities that had not been correctly implemented (fixes #6586).

This commit is contained in:
LevelX2 2020-05-29 14:41:24 +02:00
parent 40c01a04c4
commit 32ce1d85e9
5 changed files with 102 additions and 94 deletions

View file

@ -1,7 +1,6 @@
package mage.cards.a; package mage.cards.a;
import mage.MageInt; import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.DevotionCount; import mage.abilities.dynamicvalue.common.DevotionCount;
import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.CreateTokenEffect;
@ -12,17 +11,27 @@ import mage.constants.*;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent; import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.PermanentToken;
import mage.game.permanent.token.SatyrCantBlockToken; import mage.game.permanent.token.SatyrCantBlockToken;
import java.util.Objects;
import java.util.UUID; 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 * @author TheElk801
*/ */
public final class AnaxHardenedInTheForge extends CardImpl { 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) { public AnaxHardenedInTheForge(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{1}{R}{R}"); 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") .setText("{this}'s power is equal to your devotion to red")
).addHint(DevotionCount.R.getHint())); ).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. // Whenever Anax or another nontoken creature you control dies, create a 1/1 red Satyr creature token
this.addAbility(new AnaxHardenedInTheForgeTriggeredAbility()); // 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) { private AnaxHardenedInTheForge(final AnaxHardenedInTheForge card) {
@ -52,10 +62,10 @@ public final class AnaxHardenedInTheForge extends CardImpl {
} }
} }
class AnaxHardenedInTheForgeTriggeredAbility extends TriggeredAbilityImpl { class AnaxHardenedInTheForgeTriggeredAbility extends DiesThisOrAnotherCreatureTriggeredAbility {
AnaxHardenedInTheForgeTriggeredAbility() { AnaxHardenedInTheForgeTriggeredAbility(Effect effect, boolean optional, FilterCreaturePermanent filter) {
super(Zone.BATTLEFIELD, null, false); super(effect, optional, filter);
} }
private AnaxHardenedInTheForgeTriggeredAbility(final AnaxHardenedInTheForgeTriggeredAbility ability) { private AnaxHardenedInTheForgeTriggeredAbility(final AnaxHardenedInTheForgeTriggeredAbility ability) {
@ -67,33 +77,21 @@ class AnaxHardenedInTheForgeTriggeredAbility extends TriggeredAbilityImpl {
return new AnaxHardenedInTheForgeTriggeredAbility(this); return new AnaxHardenedInTheForgeTriggeredAbility(this);
} }
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
}
@Override @Override
public boolean checkTrigger(GameEvent event, Game game) { public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event; if (super.checkTrigger(event, game)) {
if (!zEvent.isDiesEvent()) { int tokenCount = ((ZoneChangeEvent) event).getTarget().getPower().getValue() > 3 ? 2 : 1;
return false; this.getEffects().clear();
this.addEffect(new CreateTokenEffect(new SatyrCantBlockToken(), tokenCount));
return true;
} }
if (!zEvent.getTarget().getId().equals(getSourceId()) return false;
&& (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;
} }
@Override @Override
public String getRule() { public String getRule() {
return "Whenever {this} or another nontoken creature you control dies, " + return "Whenever {this} or another nontoken creature you control dies, "
"create a 1/1 red Satyr creature token with \"This creature can't block.\" " + + "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."; + "If the creature had power 4 or greater, create two of those tokens instead.";
} }
} }

View file

@ -1,22 +1,17 @@
package mage.cards.p; package mage.cards.p;
import java.util.UUID; import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.MageObject; import mage.abilities.common.DiesThisOrAnotherCreatureTriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.CreateTokenEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone; import mage.constants.TargetController;
import mage.game.Game; import mage.filter.common.FilterCreaturePermanent;
import mage.game.events.GameEvent; import mage.filter.predicate.Predicates;
import mage.game.events.GameEvent.EventType; import mage.filter.predicate.permanent.TokenPredicate;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentToken;
import mage.game.permanent.token.EldraziSpawnToken; import mage.game.permanent.token.EldraziSpawnToken;
/** /**
@ -25,15 +20,24 @@ import mage.game.permanent.token.EldraziSpawnToken;
*/ */
public final class PawnOfUlamog extends CardImpl { 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) { 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.VAMPIRE);
this.subtype.add(SubType.SHAMAN); this.subtype.add(SubType.SHAMAN);
this.power = new MageInt(2); this.power = new MageInt(2);
this.toughness = 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) { public PawnOfUlamog(final PawnOfUlamog card) {
@ -45,48 +49,3 @@ public final class PawnOfUlamog extends CardImpl {
return new PawnOfUlamog(this); 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}.\"";
}
}

View file

@ -9,10 +9,17 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
* Created by alexsandro on 06/03/17. * Created by alexsandro on 06/03/17.
*/ */
public class SakashimaTheImpostorTest extends CardTestPlayerBase { public class SakashimaTheImpostorTest extends CardTestPlayerBase {
@Test @Test
public void copySpellStutterTest() { 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, playerA, "Spellstutter Sprite", 1);
addCard(Zone.BATTLEFIELD, playerB, "Island", 4); 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); addCard(Zone.HAND, playerB, "Sakashima the Impostor", 4);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Sakashima the Impostor"); 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); 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);
}
} }

View file

@ -171,21 +171,24 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge
} }
} }
} }
if (isLeavesTheBattlefieldTrigger()) {
source = zce.getTarget();
}
break;
case DESTROYED_PERMANENT: case DESTROYED_PERMANENT:
if (isLeavesTheBattlefieldTrigger()) { if (isLeavesTheBattlefieldTrigger()) {
if (event.getType() == EventType.DESTROYED_PERMANENT) { source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD);
source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD);
} else if (((ZoneChangeEvent) event).getTarget() != null) {
source = ((ZoneChangeEvent) event).getTarget();
} else {
source = game.getLastKnownInformation(getSourceId(), event.getZone());
}
} }
break;
case PHASED_OUT: case PHASED_OUT:
case PHASED_IN: case PHASED_IN:
if (isLeavesTheBattlefieldTrigger()) {
source = game.getLastKnownInformation(getSourceId(), event.getZone());
}
if (this.zone == Zone.ALL || game.getLastKnownInformation(getSourceId(), zone) != null) { if (this.zone == Zone.ALL || game.getLastKnownInformation(getSourceId(), zone) != null) {
return this.hasSourceObjectAbility(game, source, event); return this.hasSourceObjectAbility(game, source, event);
} }
break;
} }
return super.isInUseableZone(game, source, event); return super.isInUseableZone(game, source, event);
} }

View file

@ -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) { public ZoneChangeAllTriggeredAbility(Zone zone, Zone fromZone, Zone toZone, Effect effect, FilterPermanent filter, String rule, boolean optional) {
super(zone, effect, optional); super(zone, effect, optional);
if (fromZone == Zone.BATTLEFIELD) {
setLeavesTheBattlefieldTrigger(true);
}
this.fromZone = fromZone; this.fromZone = fromZone;
this.toZone = toZone; this.toZone = toZone;
this.rule = rule; this.rule = rule;