diff --git a/Mage.Sets/src/mage/cards/a/AjanisLastStand.java b/Mage.Sets/src/mage/cards/a/AjanisLastStand.java index d0288164c7ea..a2697f89c90d 100644 --- a/Mage.Sets/src/mage/cards/a/AjanisLastStand.java +++ b/Mage.Sets/src/mage/cards/a/AjanisLastStand.java @@ -1,6 +1,8 @@ package mage.cards.a; import java.util.UUID; + +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.DiscardedByOpponentTriggeredAbility; import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; @@ -63,6 +65,7 @@ public AjanisLastStandTriggeredAbility() { new CreateTokenEffect(new AvatarToken2()), new SacrificeSourceCost() ), false); + setLeavesTheBattlefieldTrigger(true); } private AjanisLastStandTriggeredAbility(final AjanisLastStandTriggeredAbility ability) { @@ -98,4 +101,9 @@ public String getRule() { + "you may sacrifice {this}. " + "If you do, create a 4/4 white Avatar creature token with flying."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java b/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java index 16948746ae7c..33c317738cec 100644 --- a/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java +++ b/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java @@ -1,6 +1,7 @@ package mage.cards.a; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; @@ -122,6 +123,7 @@ class AthreosDiesCreatureTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, effect, optional); this.filter = filter; setTriggerPhrase("Whenever " + filter.getMessage() + " dies, "); + setLeavesTheBattlefieldTrigger(true); } private AthreosDiesCreatureTriggeredAbility(AthreosDiesCreatureTriggeredAbility ability) { @@ -153,4 +155,9 @@ public boolean checkTrigger(GameEvent event, Game game) { } return true; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/a/AthreosShroudVeiled.java b/Mage.Sets/src/mage/cards/a/AthreosShroudVeiled.java index f0f2df5cc95d..8debc0e7bcbc 100644 --- a/Mage.Sets/src/mage/cards/a/AthreosShroudVeiled.java +++ b/Mage.Sets/src/mage/cards/a/AthreosShroudVeiled.java @@ -80,6 +80,7 @@ class AthreosShroudVeiledTriggeredAbility extends TriggeredAbilityImpl { AthreosShroudVeiledTriggeredAbility() { super(Zone.BATTLEFIELD, null, false); + setLeavesTheBattlefieldTrigger(true); } private AthreosShroudVeiledTriggeredAbility(final AthreosShroudVeiledTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/a/AvacynsCollar.java b/Mage.Sets/src/mage/cards/a/AvacynsCollar.java index dac578fcc710..1c6baec7753f 100644 --- a/Mage.Sets/src/mage/cards/a/AvacynsCollar.java +++ b/Mage.Sets/src/mage/cards/a/AvacynsCollar.java @@ -1,6 +1,7 @@ package mage.cards.a; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; @@ -60,6 +61,7 @@ class AvacynsCollarTriggeredAbility extends TriggeredAbilityImpl { public AvacynsCollarTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new SpiritWhiteToken())); + setLeavesTheBattlefieldTrigger(true); } private AvacynsCollarTriggeredAbility(final AvacynsCollarTriggeredAbility ability) { @@ -91,4 +93,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever equipped creature dies, if it was a Human, create a 1/1 white Spirit creature token with flying."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/b/Bereavement.java b/Mage.Sets/src/mage/cards/b/Bereavement.java index 9f90fc52865a..05fb4ff62c11 100644 --- a/Mage.Sets/src/mage/cards/b/Bereavement.java +++ b/Mage.Sets/src/mage/cards/b/Bereavement.java @@ -1,6 +1,8 @@ package mage.cards.b; import java.util.UUID; + +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.discard.DiscardTargetEffect; import mage.cards.CardImpl; @@ -41,6 +43,7 @@ class BereavementTriggeredAbility extends TriggeredAbilityImpl { BereavementTriggeredAbility() { super(Zone.BATTLEFIELD, new DiscardTargetEffect(1)); + setLeavesTheBattlefieldTrigger(true); } private BereavementTriggeredAbility(final BereavementTriggeredAbility ability) { @@ -73,4 +76,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a green creature dies, its controller discards a card."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java b/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java index a89d8103604a..0be4df74c268 100644 --- a/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java +++ b/Mage.Sets/src/mage/cards/b/BridgeFromBelow.java @@ -65,6 +65,7 @@ class BridgeFromBelowAbility extends TriggeredAbilityImpl { this.filter = filter; this.withInterveningIf(SourceInGraveyardCondition.instance); setTriggerPhrase(filter.getMessage()); + setLeavesTheBattlefieldTrigger(true); // it's not required for Bridge from Below, but better to keep same code style and verify pass } private BridgeFromBelowAbility(final BridgeFromBelowAbility ability) { diff --git a/Mage.Sets/src/mage/cards/c/CallerOfTheClaw.java b/Mage.Sets/src/mage/cards/c/CallerOfTheClaw.java index 6c9e7cf4a97d..2a514af68ca6 100644 --- a/Mage.Sets/src/mage/cards/c/CallerOfTheClaw.java +++ b/Mage.Sets/src/mage/cards/c/CallerOfTheClaw.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -39,6 +38,7 @@ public CallerOfTheClaw(UUID ownerId, CardSetInfo setInfo) { // Flash this.addAbility(FlashAbility.getInstance()); + // When Caller of the Claw enters the battlefield, create a 2/2 green Bear creature token for each nontoken creature put into your graveyard from the battlefield this turn. this.getSpellAbility().addWatcher(new CallerOfTheClawWatcher()); Effect effect = new CreateTokenEffect(new BearToken(), new CallerOfTheClawDynamicValue()); diff --git a/Mage.Sets/src/mage/cards/c/CarthTheLion.java b/Mage.Sets/src/mage/cards/c/CarthTheLion.java index 5d42400bf1d1..af484ad7a792 100644 --- a/Mage.Sets/src/mage/cards/c/CarthTheLion.java +++ b/Mage.Sets/src/mage/cards/c/CarthTheLion.java @@ -58,6 +58,7 @@ public CarthTheLionTriggeredAbility() { super(Zone.BATTLEFIELD, new LookLibraryAndPickControllerEffect( 7, 1, filter, PutCards.HAND, PutCards.BOTTOM_RANDOM)); setTriggerPhrase("Whenever {this} enters or a planeswalker you control dies, "); + setLeavesTheBattlefieldTrigger(true); } private CarthTheLionTriggeredAbility(final CarthTheLionTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java b/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java index 5b527f865a0e..951111802264 100644 --- a/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java +++ b/Mage.Sets/src/mage/cards/c/ChainerNightmareAdept.java @@ -122,10 +122,10 @@ class ChainerNightmareAdeptWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST) { - if (event.getAdditionalReference() == null) { + if (event.getApprovingObject() == null) { return; } - morMap.computeIfAbsent(event.getAdditionalReference().getApprovingMageObjectReference(), m -> new HashMap<>()) + morMap.computeIfAbsent(event.getApprovingObject().getApprovingMageObjectReference(), m -> new HashMap<>()) .compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1)); } } diff --git a/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java b/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java index 9142b8bcffb5..1fb0d5e1482f 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java +++ b/Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java @@ -150,10 +150,10 @@ class ChandraHopesBeaconWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() != GameEvent.EventType.SPELL_CAST - || event.getAdditionalReference() == null) { + || event.getApprovingObject() == null) { return; } - MageObjectReference mor = event.getAdditionalReference().getApprovingMageObjectReference(); + MageObjectReference mor = event.getApprovingObject().getApprovingMageObjectReference(); Spell spell = game.getSpell(event.getTargetId()); if (mor == null || spell == null) { return; diff --git a/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java b/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java index 014bd5b749ef..6eca6368d9e5 100644 --- a/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java +++ b/Mage.Sets/src/mage/cards/c/ChissGoriaForgeTyrant.java @@ -194,10 +194,10 @@ class ChissGoriaForgeTyrantWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getApprovingObject() == null) { return; } - MageObjectReference mor = event.getAdditionalReference().getApprovingMageObjectReference(); + MageObjectReference mor = event.getApprovingObject().getApprovingMageObjectReference(); Spell spell = game.getSpell(event.getTargetId()); if (mor == null || spell == null) { return; diff --git a/Mage.Sets/src/mage/cards/c/Chronozoa.java b/Mage.Sets/src/mage/cards/c/Chronozoa.java index 39edd855a93a..97f3248396b0 100644 --- a/Mage.Sets/src/mage/cards/c/Chronozoa.java +++ b/Mage.Sets/src/mage/cards/c/Chronozoa.java @@ -33,7 +33,7 @@ public Chronozoa(UUID ownerId, CardSetInfo setInfo) { // Vanishing 3 (This permanent enters the battlefield with three time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.) this.addAbility(new VanishingAbility(3)); - // When Chronozoa is put into a graveyard from play, if it had no time counters on it, create two tokens that are copies of it. + // When Chronozoa dies, if it had no time counters on it, create two tokens that are copies of it. Effect effect = new CreateTokenCopySourceEffect(2); effect.setText("create two tokens that are copies of it"); this.addAbility(new ConditionalInterveningIfTriggeredAbility(new DiesSourceTriggeredAbility(effect, false), diff --git a/Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java b/Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java index b8f3a117abf0..40b9f884318d 100644 --- a/Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java +++ b/Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java @@ -237,15 +237,15 @@ public void watch(GameEvent event, Game game) { cardsAllowedToBePlayedOrCast.add(new MageObjectReference(mainCard, game)); return; } - if (event.getAdditionalReference() == null - || !MageIdentifier.CoramTheUndertakerWatcher.equals(event.getAdditionalReference().getApprovingAbility().getIdentifier())) { + if (event.getApprovingObject() == null + || !MageIdentifier.CoramTheUndertakerWatcher.equals(event.getApprovingObject().getApprovingAbility().getIdentifier())) { return; } if (event.getType() == GameEvent.EventType.LAND_PLAYED) { - landPlayedForSource.add(event.getAdditionalReference().getApprovingMageObjectReference()); + landPlayedForSource.add(event.getApprovingObject().getApprovingMageObjectReference()); } if (event.getType() == GameEvent.EventType.SPELL_CAST) { - spellCastForSource.add(event.getAdditionalReference().getApprovingMageObjectReference()); + spellCastForSource.add(event.getApprovingObject().getApprovingMageObjectReference()); } } diff --git a/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java b/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java index 8cf9f4510b2d..adce8898a578 100644 --- a/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java +++ b/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java @@ -226,7 +226,7 @@ public void watch(GameEvent event, Game game) { && event.getPlayerId() != null) { decrementCastAvailable( event.getPlayerId(), - event.getAdditionalReference().getApprovingMageObjectReference() + event.getApprovingObject().getApprovingMageObjectReference() ); } } diff --git a/Mage.Sets/src/mage/cards/d/DaxosBlessedByTheSun.java b/Mage.Sets/src/mage/cards/d/DaxosBlessedByTheSun.java index 8b7b30d757eb..774f1cbf73fa 100644 --- a/Mage.Sets/src/mage/cards/d/DaxosBlessedByTheSun.java +++ b/Mage.Sets/src/mage/cards/d/DaxosBlessedByTheSun.java @@ -54,6 +54,7 @@ class DaxosBlessedByTheSunAbility extends TriggeredAbilityImpl { DaxosBlessedByTheSunAbility() { super(Zone.BATTLEFIELD, new GainLifeEffect(1)); + setLeavesTheBattlefieldTrigger(true); } private DaxosBlessedByTheSunAbility(DaxosBlessedByTheSunAbility ability) { diff --git a/Mage.Sets/src/mage/cards/d/DeathTyrant.java b/Mage.Sets/src/mage/cards/d/DeathTyrant.java index 5c2826e34514..18d0feed6a48 100644 --- a/Mage.Sets/src/mage/cards/d/DeathTyrant.java +++ b/Mage.Sets/src/mage/cards/d/DeathTyrant.java @@ -62,6 +62,7 @@ class DeathTyrantTriggeredAbility extends TriggeredAbilityImpl { DeathTyrantTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new ZombieToken())); setTriggerPhrase("Whenever an attacking creature you control or a blocking creature an opponent controls dies, "); + setLeavesTheBattlefieldTrigger(true); } private DeathTyrantTriggeredAbility(final DeathTyrantTriggeredAbility ability) { @@ -94,7 +95,7 @@ public boolean checkTrigger(GameEvent event, Game game) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage.Sets/src/mage/cards/d/DeathsPresence.java b/Mage.Sets/src/mage/cards/d/DeathsPresence.java index 619f967e0b1e..8ccfce29caca 100644 --- a/Mage.Sets/src/mage/cards/d/DeathsPresence.java +++ b/Mage.Sets/src/mage/cards/d/DeathsPresence.java @@ -1,8 +1,8 @@ - - package mage.cards.d; import java.util.UUID; + +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.CardImpl; @@ -25,7 +25,6 @@ public final class DeathsPresence extends CardImpl { public DeathsPresence(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{5}{G}"); - // Whenever a creature you control dies, put X +1/+1 counters on target creature you control, where X is the power of the creature that died. this.addAbility(new DeathsPresenceTriggeredAbility()); } @@ -44,6 +43,7 @@ class DeathsPresenceTriggeredAbility extends TriggeredAbilityImpl { public DeathsPresenceTriggeredAbility() { super(Zone.BATTLEFIELD, null); + setLeavesTheBattlefieldTrigger(true); } private DeathsPresenceTriggeredAbility(final DeathsPresenceTriggeredAbility ability) { @@ -80,4 +80,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a creature you control dies, put X +1/+1 counters on target creature you control, where X is the power of the creature that died."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/d/DiabolicServitude.java b/Mage.Sets/src/mage/cards/d/DiabolicServitude.java index bc8f05ca3873..8e9323877928 100644 --- a/Mage.Sets/src/mage/cards/d/DiabolicServitude.java +++ b/Mage.Sets/src/mage/cards/d/DiabolicServitude.java @@ -1,6 +1,8 @@ package mage.cards.d; import java.util.UUID; + +import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; @@ -91,6 +93,7 @@ class DiabolicServitudeCreatureDiesTriggeredAbility extends TriggeredAbilityImpl public DiabolicServitudeCreatureDiesTriggeredAbility() { super(Zone.BATTLEFIELD, new DiabolicServitudeExileCreatureEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private DiabolicServitudeCreatureDiesTriggeredAbility(final DiabolicServitudeCreatureDiesTriggeredAbility ability) { @@ -123,6 +126,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "When the creature put onto the battlefield with {this} dies, exile it and return {this} to its owner's hand."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class DiabolicServitudeExileCreatureEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java b/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java index 0e35ac6c7bbf..9db8a67e7eef 100644 --- a/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java +++ b/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java @@ -1,6 +1,7 @@ package mage.cards.d; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.LoseLifeTargetEffect; @@ -67,6 +68,7 @@ class DiregrafCaptainTriggeredAbility extends TriggeredAbilityImpl { public DiregrafCaptainTriggeredAbility() { super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(1), false); this.addTarget(new TargetOpponent()); + this.setLeavesTheBattlefieldTrigger(true); } private DiregrafCaptainTriggeredAbility(final DiregrafCaptainTriggeredAbility ability) { @@ -99,4 +101,9 @@ public DiregrafCaptainTriggeredAbility copy() { public String getRule() { return "Whenever another Zombie you control dies, target opponent loses 1 life."; } -} + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/Dreadhound.java b/Mage.Sets/src/mage/cards/d/Dreadhound.java index be6fe64a3489..3df8fc02676b 100644 --- a/Mage.Sets/src/mage/cards/d/Dreadhound.java +++ b/Mage.Sets/src/mage/cards/d/Dreadhound.java @@ -53,6 +53,7 @@ class DreadhoundTriggeredAbility extends TriggeredAbilityImpl { public DreadhoundTriggeredAbility() { super(Zone.BATTLEFIELD, new LoseLifeOpponentsEffect(1)); setTriggerPhrase("Whenever a creature dies or a creature card is put into a graveyard from a library, "); + setLeavesTheBattlefieldTrigger(true); } private DreadhoundTriggeredAbility(final DreadhoundTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/e/EndlessEvil.java b/Mage.Sets/src/mage/cards/e/EndlessEvil.java index e259defedc25..d1cf80be8c08 100644 --- a/Mage.Sets/src/mage/cards/e/EndlessEvil.java +++ b/Mage.Sets/src/mage/cards/e/EndlessEvil.java @@ -1,5 +1,6 @@ package mage.cards.e; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -97,6 +98,7 @@ class EndlessEvilBounceAbility extends TriggeredAbilityImpl { public EndlessEvilBounceAbility() { super(Zone.BATTLEFIELD, new ReturnToHandSourceEffect(false, true)); + setLeavesTheBattlefieldTrigger(true); } private EndlessEvilBounceAbility(final EndlessEvilBounceAbility effect) { @@ -126,4 +128,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "When enchanted creature dies, if that creature was a Horror, return {this} to its owner's hand."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/e/EnigmaSphinx.java b/Mage.Sets/src/mage/cards/e/EnigmaSphinx.java index 797e6e6f5676..a1f0488b0cc9 100644 --- a/Mage.Sets/src/mage/cards/e/EnigmaSphinx.java +++ b/Mage.Sets/src/mage/cards/e/EnigmaSphinx.java @@ -3,6 +3,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -65,6 +66,7 @@ public EnigmaSphinxTriggeredAbility(Effect effect) { public EnigmaSphinxTriggeredAbility(Effect effect, boolean optional) { super(Zone.ALL, effect, optional); setTriggerPhrase("When {this} is put into your graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private EnigmaSphinxTriggeredAbility(final EnigmaSphinxTriggeredAbility ability) { @@ -94,6 +96,11 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class EnigmaSphinxEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java b/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java index d42736a2ec93..5f098d88de50 100644 --- a/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java +++ b/Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java @@ -201,9 +201,9 @@ class EvelynTheCovetousWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if ((event.getType() == GameEvent.EventType.SPELL_CAST || event.getType() == GameEvent.EventType.LAND_PLAYED) - && event.getAdditionalReference() != null) { + && event.getApprovingObject() != null) { usedMap.computeIfAbsent( - event.getAdditionalReference() + event.getApprovingObject() .getApprovingMageObjectReference(), x -> new HashSet<>() ).add(event.getPlayerId()); diff --git a/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java b/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java index ed0621fd7e1f..01cae39b36b3 100644 --- a/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java +++ b/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java @@ -1,6 +1,8 @@ package mage.cards.f; import mage.MageInt; +import mage.MageObject; +import mage.abilities.AbilityImpl; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeTargetEffect; @@ -51,6 +53,7 @@ public FalkenrathNobleTriggeredAbility() { super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(1), false); this.addEffect(new GainLifeEffect(1)); this.addTarget(new TargetPlayer()); + setLeavesTheBattlefieldTrigger(true); } private FalkenrathNobleTriggeredAbility(final FalkenrathNobleTriggeredAbility ability) { @@ -87,4 +90,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever {this} or another creature dies, target player loses 1 life and you gain 1 life."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/f/Flameskull.java b/Mage.Sets/src/mage/cards/f/Flameskull.java index 8e7d54463327..d1101dfc6433 100644 --- a/Mage.Sets/src/mage/cards/f/Flameskull.java +++ b/Mage.Sets/src/mage/cards/f/Flameskull.java @@ -1,7 +1,6 @@ package mage.cards.f; import mage.MageInt; -import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.CantBlockAbility; @@ -132,10 +131,10 @@ class FlameskullWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() != GameEvent.EventType.SPELL_CAST - || event.getAdditionalReference() == null) { + || event.getApprovingObject() == null) { return; } - MageObjectReference mor = event.getAdditionalReference().getApprovingMageObjectReference(); + MageObjectReference mor = event.getApprovingObject().getApprovingMageObjectReference(); Spell spell = game.getSpell(event.getTargetId()); if (mor == null || spell == null) { return; diff --git a/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java b/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java index 2436daa2dab5..011cd01ca5a4 100644 --- a/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java +++ b/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java @@ -91,7 +91,7 @@ public boolean checkTrigger(GameEvent event, Game game) { if (!isControlledBy(event.getPlayerId()) || event.getZone() != Zone.LIBRARY || !event - .getAdditionalReference() + .getApprovingObject() .getApprovingMageObjectReference() .refersTo(this.getSourceObject(game), game)) { return false; diff --git a/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java b/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java index a8ffb3d46b9c..6466b2cbcad0 100644 --- a/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java +++ b/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java @@ -162,7 +162,7 @@ public GlimpseTheCosmosWatcher() { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.CAST_SPELL && event.hasApprovingIdentifier(MageIdentifier.GlimpseTheCosmosWatcher)) { - Ability approvingAbility = event.getAdditionalReference().getApprovingAbility(); + Ability approvingAbility = event.getApprovingObject().getApprovingAbility(); if (approvingAbility != null && approvingAbility.getSourceId().equals(event.getSourceId())) { sourceCards.add(game.getCard(event.getSourceId())); diff --git a/Mage.Sets/src/mage/cards/g/GraveBetrayal.java b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java index af4674f4632d..4d70006e4cc1 100644 --- a/Mage.Sets/src/mage/cards/g/GraveBetrayal.java +++ b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java @@ -2,6 +2,8 @@ package mage.cards.g; import java.util.UUID; + +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -53,6 +55,7 @@ class GraveBetrayalTriggeredAbility extends TriggeredAbilityImpl { public GraveBetrayalTriggeredAbility() { super(Zone.BATTLEFIELD, null); + setLeavesTheBattlefieldTrigger(true); } private GraveBetrayalTriggeredAbility(final GraveBetrayalTriggeredAbility ability) { @@ -92,6 +95,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a creature you don't control dies, return it to the battlefield under your control with an additional +1/+1 counter on it at the beginning of the next end step. That creature is a black Zombie in addition to its other colors and types."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class GraveBetrayalEffect extends OneShotEffect { @@ -125,7 +133,6 @@ public boolean apply(Game game, Ability source) { } return false; } - } class GraveBetrayalReplacementEffect extends ReplacementEffectImpl { diff --git a/Mage.Sets/src/mage/cards/g/GravePact.java b/Mage.Sets/src/mage/cards/g/GravePact.java index c58990753c05..4c5a036722d7 100644 --- a/Mage.Sets/src/mage/cards/g/GravePact.java +++ b/Mage.Sets/src/mage/cards/g/GravePact.java @@ -1,5 +1,6 @@ package mage.cards.g; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; @@ -29,7 +30,6 @@ public final class GravePact extends CardImpl { public GravePact(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}{B}{B}"); - // Whenever a creature you control dies, each other player sacrifices a creature. this.addAbility(new GravePactTriggeredAbility()); } @@ -49,6 +49,7 @@ class GravePactTriggeredAbility extends TriggeredAbilityImpl { public GravePactTriggeredAbility() { super(Zone.BATTLEFIELD, new GravePactEffect()); setTriggerPhrase("Whenever a creature you control dies, "); + this.setLeavesTheBattlefieldTrigger(true); } private GravePactTriggeredAbility(final GravePactTriggeredAbility ability) { @@ -74,6 +75,11 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class GravePactEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/g/GutterGrime.java b/Mage.Sets/src/mage/cards/g/GutterGrime.java index ca06176f03e0..956f177edff7 100644 --- a/Mage.Sets/src/mage/cards/g/GutterGrime.java +++ b/Mage.Sets/src/mage/cards/g/GutterGrime.java @@ -48,6 +48,7 @@ class GutterGrimeTriggeredAbility extends TriggeredAbilityImpl { public GutterGrimeTriggeredAbility() { super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.SLIME.createInstance()), false); this.addEffect(new GutterGrimeEffect()); + setLeavesTheBattlefieldTrigger(true); } private GutterGrimeTriggeredAbility(final GutterGrimeTriggeredAbility ability) { @@ -83,6 +84,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a nontoken creature you control dies, put a slime counter on {this}, then create a green Ooze creature token with \"This creature's power and toughness are each equal to the number of slime counters on {this}.\""; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class GutterGrimeEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/h/HatefulEidolon.java b/Mage.Sets/src/mage/cards/h/HatefulEidolon.java index a615cf25fc98..3a0e4298251d 100644 --- a/Mage.Sets/src/mage/cards/h/HatefulEidolon.java +++ b/Mage.Sets/src/mage/cards/h/HatefulEidolon.java @@ -1,6 +1,7 @@ package mage.cards.h; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.keyword.LifelinkAbility; import mage.cards.CardImpl; @@ -51,6 +52,7 @@ class HatefulEidolonTriggeredAbility extends TriggeredAbilityImpl { HatefulEidolonTriggeredAbility() { super(Zone.BATTLEFIELD, null, false); + setLeavesTheBattlefieldTrigger(true); } private HatefulEidolonTriggeredAbility(final HatefulEidolonTriggeredAbility ability) { @@ -105,4 +107,9 @@ public String getRule() { return "Whenever an enchanted creature dies, draw a card for each " + "Aura you controlled that was attached to it."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/h/HaukensInsight.java b/Mage.Sets/src/mage/cards/h/HaukensInsight.java index ad5a6cd5dee3..19b896ecdd0f 100644 --- a/Mage.Sets/src/mage/cards/h/HaukensInsight.java +++ b/Mage.Sets/src/mage/cards/h/HaukensInsight.java @@ -185,7 +185,7 @@ public HaukensInsightWatcher() { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST || event.getType() == GameEvent.EventType.LAND_PLAYED) { if (event.hasApprovingIdentifier(MageIdentifier.HaukensInsightWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); } } } diff --git a/Mage.Sets/src/mage/cards/h/HedonistsTrove.java b/Mage.Sets/src/mage/cards/h/HedonistsTrove.java index 650fbff8f089..04d061f13029 100644 --- a/Mage.Sets/src/mage/cards/h/HedonistsTrove.java +++ b/Mage.Sets/src/mage/cards/h/HedonistsTrove.java @@ -173,12 +173,12 @@ class HedonistsTroveWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() != GameEvent.EventType.SPELL_CAST - || event.getAdditionalReference() == null) { + || event.getApprovingObject() == null) { return; } playerMap .computeIfAbsent(event.getPlayerId(), x -> new HashSet<>()) - .add(event.getAdditionalReference().getApprovingMageObjectReference()); + .add(event.getApprovingObject().getApprovingMageObjectReference()); playerMap.get(event.getPlayerId()).removeIf(Objects::isNull); } diff --git a/Mage.Sets/src/mage/cards/i/IanMalcolmChaotician.java b/Mage.Sets/src/mage/cards/i/IanMalcolmChaotician.java index 20494b03b74e..2ec090a52d5f 100644 --- a/Mage.Sets/src/mage/cards/i/IanMalcolmChaotician.java +++ b/Mage.Sets/src/mage/cards/i/IanMalcolmChaotician.java @@ -224,7 +224,7 @@ public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST && event.hasApprovingIdentifier(MageIdentifier.IanMalcolmChaoticianWatcher)) { usedMap.computeIfAbsent( - event.getAdditionalReference() + event.getApprovingObject() .getApprovingMageObjectReference(), x -> new HashSet<>() ).add(event.getPlayerId()); diff --git a/Mage.Sets/src/mage/cards/i/IdolOfEndurance.java b/Mage.Sets/src/mage/cards/i/IdolOfEndurance.java index ce789ae71949..f57fbfb31978 100644 --- a/Mage.Sets/src/mage/cards/i/IdolOfEndurance.java +++ b/Mage.Sets/src/mage/cards/i/IdolOfEndurance.java @@ -211,10 +211,10 @@ class IdolOfEnduranceWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST) { - if (event.getAdditionalReference() == null) { + if (event.getApprovingObject() == null) { return; } - morMap.computeIfAbsent(event.getAdditionalReference().getApprovingMageObjectReference(), m -> new HashMap<>()) + morMap.computeIfAbsent(event.getApprovingObject().getApprovingMageObjectReference(), m -> new HashMap<>()) .compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1)); } } diff --git a/Mage.Sets/src/mage/cards/i/InfestedThrinax.java b/Mage.Sets/src/mage/cards/i/InfestedThrinax.java index 6aaf3d51784f..a4c24be192a3 100644 --- a/Mage.Sets/src/mage/cards/i/InfestedThrinax.java +++ b/Mage.Sets/src/mage/cards/i/InfestedThrinax.java @@ -1,7 +1,9 @@ package mage.cards.i; import mage.MageInt; +import mage.MageObject; import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; @@ -55,6 +57,7 @@ class InfestedThrinaxTriggeredAbility extends DelayedTriggeredAbility { InfestedThrinaxTriggeredAbility() { super(new CreateTokenEffect(new SaprolingToken(), SavedDamageValue.MUCH), Duration.EndOfTurn, false, false); + setLeavesTheBattlefieldTrigger(true); } private InfestedThrinaxTriggeredAbility(final InfestedThrinaxTriggeredAbility ability) { @@ -89,4 +92,9 @@ public String getRule() { return "Whenever a nontoken creature you control dies, " + "create a number of 1/1 green Saproling creature tokens equal to that creature's power."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java b/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java index 0b514ae90afa..606bec7fe1e7 100644 --- a/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java +++ b/Mage.Sets/src/mage/cards/j/JerrenCorruptedBishop.java @@ -94,6 +94,7 @@ class JerrenCorruptedBishopTriggeredAbility extends TriggeredAbilityImpl { JerrenCorruptedBishopTriggeredAbility() { super(Zone.BATTLEFIELD, new LoseLifeSourceControllerEffect(1)); this.addEffect(new CreateTokenEffect(new HumanToken())); + setLeavesTheBattlefieldTrigger(true); } private JerrenCorruptedBishopTriggeredAbility(final JerrenCorruptedBishopTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/k/KaghaShadowArchdruid.java b/Mage.Sets/src/mage/cards/k/KaghaShadowArchdruid.java index 5830ad878e6a..9f4b47eaaa39 100644 --- a/Mage.Sets/src/mage/cards/k/KaghaShadowArchdruid.java +++ b/Mage.Sets/src/mage/cards/k/KaghaShadowArchdruid.java @@ -123,7 +123,7 @@ class KaghaShadowArchdruidWatcher extends Watcher { public void watch(GameEvent event, Game game) { if ((GameEvent.EventType.SPELL_CAST.equals(event.getType()) || GameEvent.EventType.LAND_PLAYED.equals(event.getType())) && event.hasApprovingIdentifier(MageIdentifier.KaghaShadowArchdruidWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); } if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; diff --git a/Mage.Sets/src/mage/cards/k/KarmicJustice.java b/Mage.Sets/src/mage/cards/k/KarmicJustice.java index 09dcd7ea1309..68bb6406d274 100644 --- a/Mage.Sets/src/mage/cards/k/KarmicJustice.java +++ b/Mage.Sets/src/mage/cards/k/KarmicJustice.java @@ -44,6 +44,7 @@ class KarmicJusticeTriggeredAbility extends TriggeredAbilityImpl { KarmicJusticeTriggeredAbility() { super(Zone.BATTLEFIELD, new DestroyTargetEffect(), true); + this.setLeavesTheBattlefieldTrigger(true); } private KarmicJusticeTriggeredAbility(final KarmicJusticeTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/k/KayaTheInexorable.java b/Mage.Sets/src/mage/cards/k/KayaTheInexorable.java index e52b32c15e99..cc1ab89fcaf7 100644 --- a/Mage.Sets/src/mage/cards/k/KayaTheInexorable.java +++ b/Mage.Sets/src/mage/cards/k/KayaTheInexorable.java @@ -78,6 +78,7 @@ class KayaTheInexorableTriggeredAbility extends TriggeredAbilityImpl { public KayaTheInexorableTriggeredAbility() { super(Zone.ALL, null, false); + this.setLeavesTheBattlefieldTrigger(true); } private KayaTheInexorableTriggeredAbility(KayaTheInexorableTriggeredAbility ability) { @@ -107,22 +108,6 @@ public boolean checkTrigger(GameEvent event, Game game) { return false; } - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent sourcePermanent = null; - if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) { - sourcePermanent = game.getPermanent(getSourceId()); - } else { - if (game.checkShortLivingLKI(getSourceId(), Zone.BATTLEFIELD)) { - sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); - } - } - if (sourcePermanent == null) { - return false; - } - return hasSourceObjectAbility(game, sourcePermanent, event); - } - @Override public KayaTheInexorableTriggeredAbility copy() { return new KayaTheInexorableTriggeredAbility(this); diff --git a/Mage.Sets/src/mage/cards/k/KayasGhostform.java b/Mage.Sets/src/mage/cards/k/KayasGhostform.java index 0cd641eb58ca..de7433f48047 100644 --- a/Mage.Sets/src/mage/cards/k/KayasGhostform.java +++ b/Mage.Sets/src/mage/cards/k/KayasGhostform.java @@ -60,6 +60,7 @@ class KayasGhostformTriggeredAbility extends TriggeredAbilityImpl { KayasGhostformTriggeredAbility() { super(Zone.ALL, new ReturnToBattlefieldUnderYourControlAttachedEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private KayasGhostformTriggeredAbility(final KayasGhostformTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/k/KessDissidentMage.java b/Mage.Sets/src/mage/cards/k/KessDissidentMage.java index 64f349b7699f..7deead6e67fb 100644 --- a/Mage.Sets/src/mage/cards/k/KessDissidentMage.java +++ b/Mage.Sets/src/mage/cards/k/KessDissidentMage.java @@ -178,9 +178,9 @@ public void watch(GameEvent event, Game game) { && event.hasApprovingIdentifier(MageIdentifier.KessDissidentMageWatcher)) { Spell spell = (Spell) game.getObject(event.getTargetId()); if (spell != null) { - allowingObjects.add(event.getAdditionalReference().getApprovingMageObjectReference()); + allowingObjects.add(event.getApprovingObject().getApprovingMageObjectReference()); castSpells.put(new MageObjectReference(spell.getMainCard().getId(), game), - event.getAdditionalReference().getApprovingAbility().getSourceId()); + event.getApprovingObject().getApprovingAbility().getSourceId()); } } } diff --git a/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java b/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java index b02bc89141e4..4e6aa44c4b6a 100644 --- a/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java +++ b/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java @@ -170,7 +170,7 @@ public void watch(GameEvent event, Game game) { morMap.values().removeIf(Set::isEmpty); return; } - if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getApprovingObject() == null) { return; } Spell spell = game.getSpell(event.getTargetId()); @@ -178,7 +178,7 @@ public void watch(GameEvent event, Game game) { return; } morMap.getOrDefault( - event.getAdditionalReference().getApprovingMageObjectReference(), Collections.emptySet() + event.getApprovingObject().getApprovingMageObjectReference(), Collections.emptySet() ).removeIf(set -> set .stream() .anyMatch(mor -> mor.getSourceId().equals(spell.getMainCard().getId()) diff --git a/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java b/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java index fd540af7c718..866c5237622f 100644 --- a/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java +++ b/Mage.Sets/src/mage/cards/l/LobeliaDefenderOfBagEnd.java @@ -222,10 +222,10 @@ class LobeliaDefenderOfBagEndWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST || event.getType() == GameEvent.EventType.PLAY_LAND) { - if (event.getAdditionalReference() == null) { + if (event.getApprovingObject() == null) { return; } - morMap.computeIfAbsent(event.getAdditionalReference().getApprovingMageObjectReference(), m -> new HashMap<>()) + morMap.computeIfAbsent(event.getApprovingObject().getApprovingMageObjectReference(), m -> new HashMap<>()) .compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1)); } } diff --git a/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java b/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java index 8da7d9ba86df..916f03d67774 100644 --- a/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java +++ b/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java @@ -1,6 +1,7 @@ package mage.cards.l; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; @@ -56,6 +57,7 @@ class LuminousBroodmothTriggeredAbility extends TriggeredAbilityImpl { LuminousBroodmothTriggeredAbility() { super(Zone.BATTLEFIELD, new LuminousBroodmothEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private LuminousBroodmothTriggeredAbility(final LuminousBroodmothTriggeredAbility ability) { @@ -97,6 +99,11 @@ public String getRule() { return "Whenever a creature you control without flying dies, " + "return it to the battlefield under its owner's control with a flying counter on it."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class LuminousBroodmothEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java b/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java index 3366ed15c9b1..4bb11c18f647 100644 --- a/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java +++ b/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java @@ -70,6 +70,7 @@ public LyndeCheerfulTormentorCurseDiesTriggeredAbility() { new LyndeCheerfulTormentorReturnCurseEffect() ) )); + setLeavesTheBattlefieldTrigger(true); } private LyndeCheerfulTormentorCurseDiesTriggeredAbility(final LyndeCheerfulTormentorCurseDiesTriggeredAbility ability) { @@ -102,8 +103,8 @@ public boolean checkTrigger(GameEvent event, Game game) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } @Override diff --git a/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java b/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java index 06518125704d..a4e8a5a4ab17 100644 --- a/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java +++ b/Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java @@ -148,14 +148,14 @@ class MaestrosAscendancyWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST && event.hasApprovingIdentifier(MageIdentifier.MaestrosAscendencyAlternateCast) - && event.getAdditionalReference() != null) { + && event.getApprovingObject() != null) { playerMap.computeIfAbsent( - event.getAdditionalReference() + event.getApprovingObject() .getApprovingMageObjectReference(), x -> new HashSet<>() ).add(event.getPlayerId()); spellMap.computeIfAbsent( - event.getAdditionalReference() + event.getApprovingObject() .getApprovingMageObjectReference(), x -> new HashSet<>() ).add(new MageObjectReference(event.getTargetId(), game)); diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java b/Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java index 9f28c451fd0a..81fc10fb6841 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java @@ -2,6 +2,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.PutIntoGraveFromBattlefieldAllTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; @@ -59,6 +60,7 @@ class MagusOfTheBridgeTriggeredAbility extends TriggeredAbilityImpl { public MagusOfTheBridgeTriggeredAbility() { super(Zone.BATTLEFIELD, new ExileSourceEffect()); setTriggerPhrase("When a creature is put into an opponent's graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private MagusOfTheBridgeTriggeredAbility(final MagusOfTheBridgeTriggeredAbility ability) { @@ -86,4 +88,9 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java b/Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java index 78d3dd1e5626..f93dee68cf20 100644 --- a/Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java +++ b/Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java @@ -117,11 +117,11 @@ public MarchOfRecklessJoyWatcher() { @Override public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getApprovingObject() == null) { return; } morMap.compute(event - .getAdditionalReference() + .getApprovingObject() .getApprovingMageObjectReference(), CardUtil::setOrIncrementValue ); diff --git a/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java b/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java index e4358d79f2b7..a2593ee86a91 100644 --- a/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java +++ b/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java @@ -3,6 +3,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -67,6 +68,7 @@ class MarchesaTheBlackRoseTriggeredAbility extends TriggeredAbilityImpl { public MarchesaTheBlackRoseTriggeredAbility() { super(Zone.BATTLEFIELD, new MarchesaTheBlackRoseEffect()); setTriggerPhrase("Whenever a creature you control with a +1/+1 counter on it dies, "); + setLeavesTheBattlefieldTrigger(true); } private MarchesaTheBlackRoseTriggeredAbility(final MarchesaTheBlackRoseTriggeredAbility ability) { @@ -100,6 +102,11 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class MarchesaTheBlackRoseEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java b/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java index 70f9e008ffc4..be800e48089a 100644 --- a/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java +++ b/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java @@ -1,6 +1,7 @@ package mage.cards.m; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; @@ -165,6 +166,7 @@ class MariTheKillingQuillCreatureDiesAbility extends TriggeredAbilityImpl { public MariTheKillingQuillCreatureDiesAbility() { super(Zone.BATTLEFIELD, new MariTheKillingQuillExileCreatureEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private MariTheKillingQuillCreatureDiesAbility(final MariTheKillingQuillCreatureDiesAbility ability) { @@ -202,6 +204,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a creature an opponent controls dies, exile it with a hit counter on it."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class MariTheKillingQuillExileCreatureEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MartyrsBond.java b/Mage.Sets/src/mage/cards/m/MartyrsBond.java index 0eb4d9d7ad08..7183de400720 100644 --- a/Mage.Sets/src/mage/cards/m/MartyrsBond.java +++ b/Mage.Sets/src/mage/cards/m/MartyrsBond.java @@ -1,5 +1,6 @@ package mage.cards.m; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -50,6 +51,7 @@ class MartyrsBondTriggeredAbility extends TriggeredAbilityImpl { public MartyrsBondTriggeredAbility() { super(Zone.BATTLEFIELD, new MartyrsBondEffect()); + setLeavesTheBattlefieldTrigger(true); } private MartyrsBondTriggeredAbility(final MartyrsBondTriggeredAbility ability) { @@ -88,6 +90,10 @@ public String getRule() { return "Whenever {this} or another nonland permanent you control is put into a graveyard from the battlefield, each opponent sacrifices a permanent that shares a card type with it."; } + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class MartyrsBondEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MassacreGirl.java b/Mage.Sets/src/mage/cards/m/MassacreGirl.java index 219837f3e6a6..fa5de8db62ea 100644 --- a/Mage.Sets/src/mage/cards/m/MassacreGirl.java +++ b/Mage.Sets/src/mage/cards/m/MassacreGirl.java @@ -1,8 +1,10 @@ package mage.cards.m; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.BoostAllEffect; @@ -77,6 +79,7 @@ class MassacreGirlDelayedTriggeredAbility extends DelayedTriggeredAbility { MassacreGirlDelayedTriggeredAbility() { super(new BoostAllEffect(-1, -1, Duration.EndOfTurn, true), Duration.EndOfTurn, false); + setLeavesTheBattlefieldTrigger(true); } private MassacreGirlDelayedTriggeredAbility(final MassacreGirlDelayedTriggeredAbility ability) { @@ -103,4 +106,9 @@ public MassacreGirlDelayedTriggeredAbility copy() { public String getRule() { return "Whenever a creature dies this turn, each creature other than {this} gets -1/-1 until end of turn"; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/m/MassacreWurm.java b/Mage.Sets/src/mage/cards/m/MassacreWurm.java index a90806c07999..ef0fde681c59 100644 --- a/Mage.Sets/src/mage/cards/m/MassacreWurm.java +++ b/Mage.Sets/src/mage/cards/m/MassacreWurm.java @@ -2,6 +2,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.Effect; @@ -52,6 +53,7 @@ class MassacreWurmTriggeredAbility extends TriggeredAbilityImpl { MassacreWurmTriggeredAbility() { super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(2)); setTriggerPhrase("Whenever a creature an opponent controls dies, "); + setLeavesTheBattlefieldTrigger(true); } private MassacreWurmTriggeredAbility(final MassacreWurmTriggeredAbility ability) { @@ -81,4 +83,9 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java b/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java index 25571adba989..a69602dea490 100644 --- a/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java +++ b/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java @@ -79,6 +79,7 @@ class MillicentRestlessRevenantTriggeredAbility extends TriggeredAbilityImpl { MillicentRestlessRevenantTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new SpiritWhiteToken())); + setLeavesTheBattlefieldTrigger(true); } private MillicentRestlessRevenantTriggeredAbility(final MillicentRestlessRevenantTriggeredAbility ability) { @@ -124,8 +125,12 @@ public boolean checkTrigger(GameEvent event, Game game) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + return super.isInUseableZone(game, sourceObject, event); + } } @Override diff --git a/Mage.Sets/src/mage/cards/m/MimicVat.java b/Mage.Sets/src/mage/cards/m/MimicVat.java index 984c13b86c0e..8d7a377ec936 100644 --- a/Mage.Sets/src/mage/cards/m/MimicVat.java +++ b/Mage.Sets/src/mage/cards/m/MimicVat.java @@ -4,6 +4,8 @@ import java.util.HashSet; import java.util.Set; import java.util.UUID; + +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -60,6 +62,7 @@ class MimicVatTriggeredAbility extends TriggeredAbilityImpl { MimicVatTriggeredAbility() { super(Zone.BATTLEFIELD, new MimicVatEffect(), true); + setLeavesTheBattlefieldTrigger(true); } private MimicVatTriggeredAbility(final MimicVatTriggeredAbility ability) { @@ -105,6 +108,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return AbilityWord.IMPRINT.formatWord() + "Whenever a nontoken creature dies, you may exile that card. If you do, return each other card exiled with {this} to its owner's graveyard."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class MimicVatEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MolderBeast.java b/Mage.Sets/src/mage/cards/m/MolderBeast.java index 1346b6656566..696f0ad2c069 100644 --- a/Mage.Sets/src/mage/cards/m/MolderBeast.java +++ b/Mage.Sets/src/mage/cards/m/MolderBeast.java @@ -3,6 +3,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.keyword.TrampleAbility; @@ -47,6 +48,7 @@ class MolderBeastTriggeredAbility extends TriggeredAbilityImpl { public MolderBeastTriggeredAbility() { super(Zone.BATTLEFIELD, new BoostSourceEffect(2, 0, Duration.EndOfTurn), false); + setLeavesTheBattlefieldTrigger(true); } private MolderBeastTriggeredAbility(final MolderBeastTriggeredAbility ability) { @@ -74,4 +76,9 @@ public String getRule() { public MolderBeastTriggeredAbility copy() { return new MolderBeastTriggeredAbility(this); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/m/MuldrothaTheGravetide.java b/Mage.Sets/src/mage/cards/m/MuldrothaTheGravetide.java index 25a974f5b096..f9ec0bc9b198 100644 --- a/Mage.Sets/src/mage/cards/m/MuldrothaTheGravetide.java +++ b/Mage.Sets/src/mage/cards/m/MuldrothaTheGravetide.java @@ -146,8 +146,8 @@ public void watch(GameEvent event, Game game) { private void addPermanentTypes(GameEvent event, Card mageObject, Game game) { if (mageObject != null - && event.getAdditionalReference() != null - && MageIdentifier.MuldrothaTheGravetideWatcher.equals(event.getAdditionalReference().getApprovingAbility().getIdentifier())) { + && event.getApprovingObject() != null + && MageIdentifier.MuldrothaTheGravetideWatcher.equals(event.getApprovingObject().getApprovingAbility().getIdentifier())) { UUID playerId = null; if (mageObject instanceof Spell) { playerId = ((Spell) mageObject).getControllerId(); @@ -155,10 +155,10 @@ private void addPermanentTypes(GameEvent event, Card mageObject, Game game) { playerId = ((Permanent) mageObject).getControllerId(); } if (playerId != null) { - Set permanentTypes = sourcePlayedPermanentTypes.get(event.getAdditionalReference().getApprovingMageObjectReference()); + Set permanentTypes = sourcePlayedPermanentTypes.get(event.getApprovingObject().getApprovingMageObjectReference()); if (permanentTypes == null) { permanentTypes = EnumSet.noneOf(CardType.class); - sourcePlayedPermanentTypes.put(event.getAdditionalReference().getApprovingMageObjectReference(), permanentTypes); + sourcePlayedPermanentTypes.put(event.getApprovingObject().getApprovingMageObjectReference(), permanentTypes); } Set typesNotCast = EnumSet.noneOf(CardType.class); for (CardType cardType : mageObject.getCardType(game)) { diff --git a/Mage.Sets/src/mage/cards/m/MycoidShepherd.java b/Mage.Sets/src/mage/cards/m/MycoidShepherd.java index 9afb6ae5ce99..51f3d1f815be 100644 --- a/Mage.Sets/src/mage/cards/m/MycoidShepherd.java +++ b/Mage.Sets/src/mage/cards/m/MycoidShepherd.java @@ -51,6 +51,7 @@ class MycoidShepherdTriggeredAbility extends TriggeredAbilityImpl { public MycoidShepherdTriggeredAbility() { super(Zone.BATTLEFIELD, new GainLifeEffect(5), true); + setLeavesTheBattlefieldTrigger(true); } private MycoidShepherdTriggeredAbility(final MycoidShepherdTriggeredAbility ability) { @@ -91,4 +92,9 @@ public String getRule() { public MycoidShepherdTriggeredAbility copy() { return new MycoidShepherdTriggeredAbility(this); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java b/Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java index 1c78fd0ffde0..95eebdf01f93 100644 --- a/Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java +++ b/Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java @@ -121,7 +121,7 @@ public void watch(GameEvent event, Game game) { morMap.values().removeIf(Set::isEmpty); return; } - if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getApprovingObject() == null) { return; } Spell spell = game.getSpell(event.getTargetId()); @@ -129,7 +129,7 @@ public void watch(GameEvent event, Game game) { return; } morMap.getOrDefault( - event.getAdditionalReference().getApprovingMageObjectReference(), Collections.emptySet() + event.getApprovingObject().getApprovingMageObjectReference(), Collections.emptySet() ).removeIf(set -> set .stream() .anyMatch(mor -> mor.getSourceId().equals(spell.getMainCard().getId()) diff --git a/Mage.Sets/src/mage/cards/n/Necroskitter.java b/Mage.Sets/src/mage/cards/n/Necroskitter.java index c68159e35039..dc9742b15c41 100644 --- a/Mage.Sets/src/mage/cards/n/Necroskitter.java +++ b/Mage.Sets/src/mage/cards/n/Necroskitter.java @@ -3,6 +3,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.common.ReturnToBattlefieldUnderYourControlTargetEffect; @@ -55,6 +56,7 @@ class NecroskitterTriggeredAbility extends TriggeredAbilityImpl { public NecroskitterTriggeredAbility() { super(Zone.BATTLEFIELD, new ReturnToBattlefieldUnderYourControlTargetEffect(), true); + setLeavesTheBattlefieldTrigger(true); } private NecroskitterTriggeredAbility(final NecroskitterTriggeredAbility ability) { @@ -92,4 +94,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a creature an opponent controls with a -1/-1 counter on it dies, you may return that card to the battlefield under your control."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/n/NetherTraitor.java b/Mage.Sets/src/mage/cards/n/NetherTraitor.java index a4d9f339b6cc..3b4f04d605de 100644 --- a/Mage.Sets/src/mage/cards/n/NetherTraitor.java +++ b/Mage.Sets/src/mage/cards/n/NetherTraitor.java @@ -3,6 +3,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.costs.mana.ColoredManaCost; import mage.abilities.effects.common.DoIfCostPaid; @@ -55,6 +56,7 @@ class NetherTraitorTriggeredAbility extends TriggeredAbilityImpl { NetherTraitorTriggeredAbility(){ super(Zone.GRAVEYARD, new DoIfCostPaid(new ReturnSourceFromGraveyardToBattlefieldEffect(), new ColoredManaCost(ColoredManaSymbol.B))); + setLeavesTheBattlefieldTrigger(true); } private NetherTraitorTriggeredAbility(final NetherTraitorTriggeredAbility ability) { @@ -94,4 +96,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever another creature is put into your graveyard from the battlefield, you may pay {B}. If you do, return {this} from your graveyard to the battlefield."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/n/NimDeathmantle.java b/Mage.Sets/src/mage/cards/n/NimDeathmantle.java index 3dcefa47b45b..3dca6eb6eebe 100644 --- a/Mage.Sets/src/mage/cards/n/NimDeathmantle.java +++ b/Mage.Sets/src/mage/cards/n/NimDeathmantle.java @@ -1,5 +1,6 @@ package mage.cards.n; +import mage.MageObject; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; @@ -72,6 +73,7 @@ class NimDeathmantleTriggeredAbility extends TriggeredAbilityImpl { NimDeathmantleTriggeredAbility() { super(Zone.BATTLEFIELD, new NimDeathmantleEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private NimDeathmantleTriggeredAbility(final NimDeathmantleTriggeredAbility ability) { @@ -108,6 +110,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a nontoken creature is put into your graveyard from the battlefield, you may pay {4}. If you do, return that card to the battlefield and attach {this} to it."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class NimDeathmantleEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java b/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java index dd2780ae3d3b..26719137046b 100644 --- a/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java +++ b/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java @@ -136,7 +136,7 @@ public OneWithTheMultiverseWatcher() { public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST && event.hasApprovingIdentifier(MageIdentifier.OneWithTheMultiverseWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); } } diff --git a/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java b/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java index 4180c0560977..96a82a5c3e82 100644 --- a/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java +++ b/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java @@ -1,6 +1,7 @@ package mage.cards.o; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.abilities.keyword.LifelinkAbility; @@ -51,6 +52,7 @@ class OrahSkyclaveHierophantTriggeredAbility extends TriggeredAbilityImpl { OrahSkyclaveHierophantTriggeredAbility() { super(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect()); + setLeavesTheBattlefieldTrigger(true); } private OrahSkyclaveHierophantTriggeredAbility(final OrahSkyclaveHierophantTriggeredAbility ability) { @@ -94,4 +96,9 @@ public String getRule() { return "Whenever {this} or another Cleric you control dies, return target Cleric card " + "with lesser mana value from your graveyard to the battlefield."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java index b4b8c86adb4d..7460ec51914f 100644 --- a/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java +++ b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java @@ -70,6 +70,7 @@ class PatronOfTheVeinCreatureDiesTriggeredAbility extends TriggeredAbilityImpl { public PatronOfTheVeinCreatureDiesTriggeredAbility() { super(Zone.BATTLEFIELD, new PatronOfTheVeinExileCreatureEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private PatronOfTheVeinCreatureDiesTriggeredAbility(final PatronOfTheVeinCreatureDiesTriggeredAbility ability) { @@ -107,6 +108,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a creature an opponent controls dies, exile it and put a +1/+1 counter on each Vampire you control."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class PatronOfTheVeinExileCreatureEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/p/PeltCollector.java b/Mage.Sets/src/mage/cards/p/PeltCollector.java index 95b6af1183c0..848dd8214584 100644 --- a/Mage.Sets/src/mage/cards/p/PeltCollector.java +++ b/Mage.Sets/src/mage/cards/p/PeltCollector.java @@ -64,6 +64,7 @@ class PeltCollectorTriggeredAbility extends TriggeredAbilityImpl { PeltCollectorTriggeredAbility() { super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + setLeavesTheBattlefieldTrigger(true); } private PeltCollectorTriggeredAbility(PeltCollectorTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/p/PhantasmalImage.java b/Mage.Sets/src/mage/cards/p/PhantasmalImage.java index 9466bd0b5c34..ba73a50278f8 100644 --- a/Mage.Sets/src/mage/cards/p/PhantasmalImage.java +++ b/Mage.Sets/src/mage/cards/p/PhantasmalImage.java @@ -44,9 +44,7 @@ public PhantasmalImage(UUID ownerId, CardSetInfo setInfo) { this.power = new MageInt(0); this.toughness = new MageInt(0); - // You may have Phantasmal Image enter the battlefield as a copy of any creature - // on the battlefield, except it's an Illusion in addition to its other types and - // it has "When this creature becomes the target of a spell or ability, sacrifice it." + // You may have Phantasmal Image enter the battlefield as a copy of any creature on the battlefield, except it's an Illusion in addition to its other types and it has "When this creature becomes the target of a spell or ability, sacrifice it." Effect effect = new CopyPermanentEffect(StaticFilters.FILTER_PERMANENT_CREATURE, phantasmalImageApplier); effect.setText(effectText); this.addAbility(new EntersBattlefieldAbility(effect, true)); diff --git a/Mage.Sets/src/mage/cards/p/PiasRevolution.java b/Mage.Sets/src/mage/cards/p/PiasRevolution.java index 402bc1e6ff56..cf9897d25722 100644 --- a/Mage.Sets/src/mage/cards/p/PiasRevolution.java +++ b/Mage.Sets/src/mage/cards/p/PiasRevolution.java @@ -1,5 +1,6 @@ package mage.cards.p; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -97,6 +98,7 @@ class PiasRevolutionTriggeredAbility extends TriggeredAbilityImpl { public PiasRevolutionTriggeredAbility() { super(Zone.BATTLEFIELD, new PiasRevolutionReturnEffect(), false); setTriggerPhrase("Whenever a nontoken artifact is put into your graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private PiasRevolutionTriggeredAbility(final PiasRevolutionTriggeredAbility ability) { @@ -127,4 +129,9 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/p/ProperBurial.java b/Mage.Sets/src/mage/cards/p/ProperBurial.java index 4f2eac1f9daf..0e46e0e563a9 100644 --- a/Mage.Sets/src/mage/cards/p/ProperBurial.java +++ b/Mage.Sets/src/mage/cards/p/ProperBurial.java @@ -1,5 +1,6 @@ package mage.cards.p; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; @@ -40,6 +41,7 @@ class ProperBurialTriggeredAbility extends TriggeredAbilityImpl { public ProperBurialTriggeredAbility() { super(Zone.BATTLEFIELD, null); + setLeavesTheBattlefieldTrigger(true); } private ProperBurialTriggeredAbility(final ProperBurialTriggeredAbility ability) { @@ -76,4 +78,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a creature you control dies, you gain life equal to that creature's toughness."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/p/Psychomancer.java b/Mage.Sets/src/mage/cards/p/Psychomancer.java index e04b189242cb..ee633d13cb79 100644 --- a/Mage.Sets/src/mage/cards/p/Psychomancer.java +++ b/Mage.Sets/src/mage/cards/p/Psychomancer.java @@ -57,6 +57,7 @@ class PsychomancerTriggeredAbility extends TriggeredAbilityImpl { this.setTriggerPhrase("Whenever {this} or another nontoken artifact you control is put " + "into a graveyard from the battlefield or is put into exile from the battlefield, "); this.withFlavorWord("Harbinger of Despair"); + this.setLeavesTheBattlefieldTrigger(true); } private PsychomancerTriggeredAbility(final PsychomancerTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/p/Purgatory.java b/Mage.Sets/src/mage/cards/p/Purgatory.java index 05c5dc843277..295053af5f33 100644 --- a/Mage.Sets/src/mage/cards/p/Purgatory.java +++ b/Mage.Sets/src/mage/cards/p/Purgatory.java @@ -62,6 +62,7 @@ class PurgatoryTriggeredAbility extends TriggeredAbilityImpl { PurgatoryTriggeredAbility() { super(Zone.BATTLEFIELD, new PurgatoryExileEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private PurgatoryTriggeredAbility(final PurgatoryTriggeredAbility ability) { @@ -103,6 +104,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a nontoken creature is put into your graveyard from the battlefield, exile that card."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class PurgatoryExileEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java b/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java index edbd306c6e13..42d0d8dab13b 100644 --- a/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java +++ b/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java @@ -159,12 +159,12 @@ class RadiantScrollwielderWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getAdditionalReference() == null) { + if (event.getType() != GameEvent.EventType.SPELL_CAST || event.getApprovingObject() == null) { return; } morMap.put( new MageObjectReference(event.getSourceId(), game), - event.getAdditionalReference().getApprovingMageObjectReference() + event.getApprovingObject().getApprovingMageObjectReference() ); } diff --git a/Mage.Sets/src/mage/cards/r/Remembrance.java b/Mage.Sets/src/mage/cards/r/Remembrance.java index 347bf2c4b583..a9fe28c4235f 100644 --- a/Mage.Sets/src/mage/cards/r/Remembrance.java +++ b/Mage.Sets/src/mage/cards/r/Remembrance.java @@ -1,6 +1,7 @@ package mage.cards.r; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.cards.CardImpl; @@ -52,6 +53,7 @@ class RemembranceTriggeredAbility extends TriggeredAbilityImpl { RemembranceTriggeredAbility() { super(Zone.BATTLEFIELD, null, true); + setLeavesTheBattlefieldTrigger(true); } private RemembranceTriggeredAbility(final RemembranceTriggeredAbility ability) { @@ -92,4 +94,9 @@ public String getRule() { "you may search your library for a card with the same name as that creature, " + "reveal it, put it into your hand, then shuffle."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/r/ReyhanLastOfTheAbzan.java b/Mage.Sets/src/mage/cards/r/ReyhanLastOfTheAbzan.java index c957bce8e86d..8223b5f7c3ca 100644 --- a/Mage.Sets/src/mage/cards/r/ReyhanLastOfTheAbzan.java +++ b/Mage.Sets/src/mage/cards/r/ReyhanLastOfTheAbzan.java @@ -63,6 +63,7 @@ class ReyhanLastOfTheAbzanTriggeredAbility extends TriggeredAbilityImpl { public ReyhanLastOfTheAbzanTriggeredAbility() { super(Zone.BATTLEFIELD, null, true); + setLeavesTheBattlefieldTrigger(true); } private ReyhanLastOfTheAbzanTriggeredAbility(final ReyhanLastOfTheAbzanTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java b/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java index b7a81dcb1a62..3bce95e4211a 100644 --- a/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java +++ b/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java @@ -1,6 +1,7 @@ package mage.cards.r; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; @@ -65,6 +66,7 @@ class RhukHexgoldNabberTriggeredAbility extends TriggeredAbilityImpl { RhukHexgoldNabberTriggeredAbility() { super(Zone.BATTLEFIELD, new RhukHexgoldNabberEffect(), true); + setLeavesTheBattlefieldTrigger(true); } private RhukHexgoldNabberTriggeredAbility(final RhukHexgoldNabberTriggeredAbility ability) { @@ -110,6 +112,15 @@ public String getRule() { return "Whenever an equipped creature you control other than {this} attacks or dies, " + "you may attach all Equipment attached to that creature to {this}."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + return super.isInUseableZone(game, sourceObject, event); + } + } } class RhukHexgoldNabberEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java b/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java index d69857b00639..850fb69a03fb 100644 --- a/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java +++ b/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java @@ -1,6 +1,7 @@ package mage.cards.r; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; @@ -70,6 +71,7 @@ class RienneAngelOfRebirthTriggeredAbility extends TriggeredAbilityImpl { RienneAngelOfRebirthTriggeredAbility() { super(Zone.BATTLEFIELD, new RienneAngelOfRebirthEffect(), false); + setLeavesTheBattlefieldTrigger(true); } private RienneAngelOfRebirthTriggeredAbility(final RienneAngelOfRebirthTriggeredAbility ability) { @@ -109,6 +111,11 @@ public String getRule() { return "Whenever another multicolored creature you control dies, " + "return it to its owner's hand at the beginning of the next end step."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class RienneAngelOfRebirthEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/s/SacredGround.java b/Mage.Sets/src/mage/cards/s/SacredGround.java index baea68dc46d9..7fa043d7480f 100644 --- a/Mage.Sets/src/mage/cards/s/SacredGround.java +++ b/Mage.Sets/src/mage/cards/s/SacredGround.java @@ -1,5 +1,6 @@ package mage.cards.s; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.cards.CardImpl; @@ -40,6 +41,7 @@ class SacredGroundTriggeredAbility extends TriggeredAbilityImpl { SacredGroundTriggeredAbility() { super(Zone.BATTLEFIELD, new ReturnFromGraveyardToBattlefieldTargetEffect()); + setLeavesTheBattlefieldTrigger(true); } private SacredGroundTriggeredAbility(final SacredGroundTriggeredAbility ability) { @@ -75,4 +77,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a spell or ability an opponent controls causes a land to be put into your graveyard from the battlefield, return that card to the battlefield."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/Sangromancer.java b/Mage.Sets/src/mage/cards/s/Sangromancer.java index fb7e27358f20..90f62fec037f 100644 --- a/Mage.Sets/src/mage/cards/s/Sangromancer.java +++ b/Mage.Sets/src/mage/cards/s/Sangromancer.java @@ -4,6 +4,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.FlyingAbility; @@ -46,9 +47,11 @@ public Sangromancer copy() { } class SangromancerFirstTriggeredAbility extends TriggeredAbilityImpl { + SangromancerFirstTriggeredAbility() { super(Zone.BATTLEFIELD, new GainLifeEffect(3), true); setTriggerPhrase("Whenever a creature an opponent controls dies, "); + setLeavesTheBattlefieldTrigger(true); } private SangromancerFirstTriggeredAbility(final SangromancerFirstTriggeredAbility ability) { @@ -75,6 +78,11 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class SangromancerSecondTriggeredAbility extends TriggeredAbilityImpl { diff --git a/Mage.Sets/src/mage/cards/s/ScrapTrawler.java b/Mage.Sets/src/mage/cards/s/ScrapTrawler.java index ed51d7e49948..80078d64cbef 100644 --- a/Mage.Sets/src/mage/cards/s/ScrapTrawler.java +++ b/Mage.Sets/src/mage/cards/s/ScrapTrawler.java @@ -1,6 +1,7 @@ package mage.cards.s; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.ReturnToHandTargetEffect; @@ -56,6 +57,7 @@ public ScrapTrawlerTriggeredAbility() { super(Zone.BATTLEFIELD, new ReturnToHandTargetEffect()); getEffects().get(0).setText("return to your hand target artifact card in your graveyard with lesser mana value"); setTriggerPhrase("Whenever {this} or another artifact you control is put into a graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private ScrapTrawlerTriggeredAbility(final ScrapTrawlerTriggeredAbility ability) { @@ -90,4 +92,9 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/Scrapheap.java b/Mage.Sets/src/mage/cards/s/Scrapheap.java index a8274337aea0..013e4aa5aaf5 100644 --- a/Mage.Sets/src/mage/cards/s/Scrapheap.java +++ b/Mage.Sets/src/mage/cards/s/Scrapheap.java @@ -1,6 +1,7 @@ package mage.cards.s; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.cards.CardImpl; @@ -45,12 +46,13 @@ public ScrapheapTriggeredAbility copy() { return new ScrapheapTriggeredAbility(this); } - private ScrapheapTriggeredAbility(final ScrapheapTriggeredAbility ability){ - super(ability); - } - public ScrapheapTriggeredAbility(){ super(Zone.BATTLEFIELD, new GainLifeEffect(1)); + setLeavesTheBattlefieldTrigger(true); + } + + private ScrapheapTriggeredAbility(final ScrapheapTriggeredAbility ability){ + super(ability); } @Override @@ -76,4 +78,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever an artifact or enchantment is put into your graveyard from the battlefield, you gain 1 life."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java b/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java index 435b71fe1398..86ec5fb253fa 100644 --- a/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java +++ b/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java @@ -2,6 +2,7 @@ import mage.MageInt; import mage.MageItem; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.keyword.SurveilEffect; import mage.abilities.keyword.MenaceAbility; @@ -53,6 +54,7 @@ class SeerOfStolenSightTriggeredAbility extends TriggeredAbilityImpl { SeerOfStolenSightTriggeredAbility() { super(Zone.BATTLEFIELD, new SurveilEffect(1)); setTriggerPhrase("Whenever one or more artifacts and/or creatures you control are put into a graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private SeerOfStolenSightTriggeredAbility(final SeerOfStolenSightTriggeredAbility ability) { @@ -85,4 +87,9 @@ public boolean checkTrigger(GameEvent event, Game game) { .map(Controllable::getControllerId) .anyMatch(this::isControlledBy); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java b/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java index a9b21caa7b54..cb450af8b943 100644 --- a/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java +++ b/Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java @@ -142,10 +142,10 @@ class SerpentsSoulJarWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST) { - if (event.getAdditionalReference() == null) { + if (event.getApprovingObject() == null) { return; } - morMap.computeIfAbsent(event.getAdditionalReference().getApprovingMageObjectReference(), m -> new HashMap<>()) + morMap.computeIfAbsent(event.getApprovingObject().getApprovingMageObjectReference(), m -> new HashMap<>()) .compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1)); return; } diff --git a/Mage.Sets/src/mage/cards/s/SerraParagon.java b/Mage.Sets/src/mage/cards/s/SerraParagon.java index 5c188cb07b41..908d79747fbb 100644 --- a/Mage.Sets/src/mage/cards/s/SerraParagon.java +++ b/Mage.Sets/src/mage/cards/s/SerraParagon.java @@ -193,7 +193,7 @@ public void watch(GameEvent event, Game game) { || event.getType() == GameEvent.EventType.LAND_PLAYED) && event.hasApprovingIdentifier(MageIdentifier.SerraParagonWatcher)) { map.computeIfAbsent( - event.getAdditionalReference() + event.getApprovingObject() .getApprovingMageObjectReference(), x -> new HashSet<>() ).add(event.getPlayerId()); diff --git a/Mage.Sets/src/mage/cards/s/ShardOfTheVoidDragon.java b/Mage.Sets/src/mage/cards/s/ShardOfTheVoidDragon.java index e82071a39ad6..9ffd88a72c98 100644 --- a/Mage.Sets/src/mage/cards/s/ShardOfTheVoidDragon.java +++ b/Mage.Sets/src/mage/cards/s/ShardOfTheVoidDragon.java @@ -59,6 +59,7 @@ class ShardOfTheVoidDragonTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance(2))); setTriggerPhrase("Whenever an artifact is put into a graveyard from the battlefield or is put into exile from the battlefield, "); withFlavorWord("Matter Absorption"); + setLeavesTheBattlefieldTrigger(true); } private ShardOfTheVoidDragonTriggeredAbility(final ShardOfTheVoidDragonTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java index 4d32904e1e6a..51cd8a005ce8 100644 --- a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java +++ b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java @@ -1,6 +1,7 @@ package mage.cards.s; import mage.MageInt; +import mage.MageObject; import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; @@ -145,6 +146,7 @@ public ShelobChildOfUngoliantTriggeredAbility(Effect effect) { super(Zone.BATTLEFIELD, effect); this.addWatcher(new ShelobChildOfUngoliantWatcher()); this.setTriggerPhrase("Whenever another creature dealt damage this turn by a Spider you controlled dies, "); + setLeavesTheBattlefieldTrigger(true); } private ShelobChildOfUngoliantTriggeredAbility(final ShelobChildOfUngoliantTriggeredAbility ability) { @@ -185,6 +187,11 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class ShelobChildOfUngoliantEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java b/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java index 635d5590fc44..53cee2407010 100644 --- a/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java +++ b/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java @@ -1,5 +1,6 @@ package mage.cards.s; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; @@ -40,6 +41,7 @@ class SlagstoneRefineryTriggeredAbility extends TriggeredAbilityImpl { SlagstoneRefineryTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new PowerstoneToken(), 1, true)); + setLeavesTheBattlefieldTrigger(true); } private SlagstoneRefineryTriggeredAbility(final SlagstoneRefineryTriggeredAbility ability) { @@ -79,4 +81,9 @@ public String getRule() { return "Whenever {this} or another nontoken artifact you control is put into a graveyard from the battlefield " + "or is put into exile from the battlefield, create a tapped Powerstone token."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/SlayersPlate.java b/Mage.Sets/src/mage/cards/s/SlayersPlate.java index 0eba2e6116df..56e9742fb9e8 100644 --- a/Mage.Sets/src/mage/cards/s/SlayersPlate.java +++ b/Mage.Sets/src/mage/cards/s/SlayersPlate.java @@ -2,6 +2,8 @@ package mage.cards.s; import java.util.UUID; + +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.GenericManaCost; @@ -56,6 +58,7 @@ class SlayersPlateTriggeredAbility extends TriggeredAbilityImpl { public SlayersPlateTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new SpiritWhiteToken())); + setLeavesTheBattlefieldTrigger(true); } private SlayersPlateTriggeredAbility(final SlayersPlateTriggeredAbility ability) { @@ -87,4 +90,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever equipped creature dies, if it was a Human, create a 1/1 white Spirit creature token with flying."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/Sporogenesis.java b/Mage.Sets/src/mage/cards/s/Sporogenesis.java index 2ff18d9e74df..61960d3cf7c2 100644 --- a/Mage.Sets/src/mage/cards/s/Sporogenesis.java +++ b/Mage.Sets/src/mage/cards/s/Sporogenesis.java @@ -1,5 +1,6 @@ package mage.cards.s; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; @@ -68,6 +69,7 @@ class SporogenesisTriggeredAbility extends TriggeredAbilityImpl { SporogenesisTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new SaprolingToken(), new SporogenesisCount()), false); + setLeavesTheBattlefieldTrigger(true); } private SporogenesisTriggeredAbility(final SporogenesisTriggeredAbility ability) { @@ -104,6 +106,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a creature with a fungus counter on it dies, create a 1/1 green Saproling creature token for each fungus counter on that creature."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class SporogenesisCount implements DynamicValue { diff --git a/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java b/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java index 91eeb02bc3c8..fad8498bc0b7 100644 --- a/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java +++ b/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java @@ -54,6 +54,7 @@ class SyrKonradTheGrimTriggeredAbility extends TriggeredAbilityImpl { SyrKonradTheGrimTriggeredAbility() { super(Zone.BATTLEFIELD, new DamagePlayersEffect(1, TargetController.OPPONENT)); + setLeavesTheBattlefieldTrigger(true); } private SyrKonradTheGrimTriggeredAbility(final SyrKonradTheGrimTriggeredAbility ability) { @@ -95,8 +96,8 @@ public boolean checkTrigger(GameEvent event, Game game) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TaekoThePatientAvalanche.java b/Mage.Sets/src/mage/cards/t/TaekoThePatientAvalanche.java index 9637aaff982b..7412aeb49487 100644 --- a/Mage.Sets/src/mage/cards/t/TaekoThePatientAvalanche.java +++ b/Mage.Sets/src/mage/cards/t/TaekoThePatientAvalanche.java @@ -67,6 +67,7 @@ class TaekoThePatientAvalancheTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, new ScryEffect(1, false)); this.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()).concatBy("and")); this.setTriggerPhrase("Whenever another creature you control leaves the battlefield, if it didn't die, "); + setLeavesTheBattlefieldTrigger(true); } private TaekoThePatientAvalancheTriggeredAbility(final TaekoThePatientAvalancheTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/t/TheScorpionGod.java b/Mage.Sets/src/mage/cards/t/TheScorpionGod.java index 632c9a2b0cbf..8a03884f9e01 100644 --- a/Mage.Sets/src/mage/cards/t/TheScorpionGod.java +++ b/Mage.Sets/src/mage/cards/t/TheScorpionGod.java @@ -3,6 +3,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -71,6 +72,7 @@ class TheScorpionGodTriggeredAbility extends TriggeredAbilityImpl { public TheScorpionGodTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), false); + setLeavesTheBattlefieldTrigger(true); } private TheScorpionGodTriggeredAbility(final TheScorpionGodTriggeredAbility ability) { @@ -105,6 +107,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a creature with a -1/-1 counter on it dies, draw a card."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class TheScorpionGodEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java b/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java index 530092dd1e58..ff220b1df5a8 100644 --- a/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java +++ b/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java @@ -112,6 +112,7 @@ class TheSkullsporeNexusTrigger extends TriggeredAbilityImpl { TheSkullsporeNexusTrigger() { super(Zone.BATTLEFIELD, null, false); + setLeavesTheBattlefieldTrigger(true); } private TheSkullsporeNexusTrigger(final TheSkullsporeNexusTrigger ability) { @@ -170,8 +171,8 @@ public String getRule() { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java b/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java index a3bdcbb7a44d..658f2bfb8c2f 100644 --- a/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java +++ b/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java @@ -3,6 +3,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; @@ -65,6 +66,7 @@ class TianaShipsCaretakerTriggeredAbility extends TriggeredAbilityImpl { TianaShipsCaretakerTriggeredAbility() { super(Zone.BATTLEFIELD, new TianaShipsCaretakerEffect(), true); setTriggerPhrase("Whenever an Aura or Equipment you control is put into a graveyard from the battlefield, "); + setLeavesTheBattlefieldTrigger(true); } private TianaShipsCaretakerTriggeredAbility(final TianaShipsCaretakerTriggeredAbility ability) { @@ -98,6 +100,11 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class TianaShipsCaretakerEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/u/UnluckyWitness.java b/Mage.Sets/src/mage/cards/u/UnluckyWitness.java index 5e984e60aa59..8181f4d3f12f 100644 --- a/Mage.Sets/src/mage/cards/u/UnluckyWitness.java +++ b/Mage.Sets/src/mage/cards/u/UnluckyWitness.java @@ -124,10 +124,10 @@ class UnluckyWitnessWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() != GameEvent.EventType.SPELL_CAST - || event.getAdditionalReference() == null) { + || event.getApprovingObject() == null) { return; } - MageObjectReference mor = event.getAdditionalReference().getApprovingMageObjectReference(); + MageObjectReference mor = event.getApprovingObject().getApprovingMageObjectReference(); Spell spell = game.getSpell(event.getTargetId()); if (mor == null || spell == null) { return; diff --git a/Mage.Sets/src/mage/cards/v/VerdantSuccession.java b/Mage.Sets/src/mage/cards/v/VerdantSuccession.java index b435f3b8a62d..3e0b602b9e2a 100644 --- a/Mage.Sets/src/mage/cards/v/VerdantSuccession.java +++ b/Mage.Sets/src/mage/cards/v/VerdantSuccession.java @@ -62,6 +62,7 @@ class VerdantSuccessionTriggeredAbility extends TriggeredAbilityImpl { VerdantSuccessionTriggeredAbility() { super(Zone.BATTLEFIELD, null, true); + setLeavesTheBattlefieldTrigger(true); } private VerdantSuccessionTriggeredAbility(final VerdantSuccessionTriggeredAbility ability) { @@ -98,6 +99,11 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever a green nontoken creature dies, that creature's controller may search their library for a card with the same name as that creature, put it onto the battlefield, then shuffle."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } class VerdantSuccessionEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java b/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java index fc7bd04d0cfd..40f766221b7a 100644 --- a/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java +++ b/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java @@ -114,7 +114,7 @@ public boolean apply(Game game, Ability source) { if (copyFromCreature != null) { game.copyPermanent(Duration.Custom, copyFromCreature, copyToCreature.getId(), source, new VesuvanShapeShifterFaceUpCopyApplier()); source.getTargets().clear(); - game.processAction(); // needed to get effects ready if copy happens in replacment and the copied abilities react of the same event (e.g. turn face up) + game.processAction(); // needed to get effects ready if copy happens in replacement and the copied abilities react of the same event (e.g. turn face up) return true; } } diff --git a/Mage.Sets/src/mage/cards/v/VillageCannibals.java b/Mage.Sets/src/mage/cards/v/VillageCannibals.java index f3d9aaec9979..9fb2dab04a72 100644 --- a/Mage.Sets/src/mage/cards/v/VillageCannibals.java +++ b/Mage.Sets/src/mage/cards/v/VillageCannibals.java @@ -3,6 +3,7 @@ import java.util.UUID; import mage.MageInt; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; @@ -48,6 +49,7 @@ class VillageCannibalsTriggeredAbility extends TriggeredAbilityImpl { public VillageCannibalsTriggeredAbility() { super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance())); setTriggerPhrase("Whenever another Human creature dies, "); + setLeavesTheBattlefieldTrigger(true); } private VillageCannibalsTriggeredAbility(final VillageCannibalsTriggeredAbility ability) { @@ -74,4 +76,9 @@ public boolean checkTrigger(GameEvent event, Game game) { return permanent != null && permanent.isCreature(game) && permanent.hasSubtype(SubType.HUMAN, game) && !permanent.getId().equals(this.getSourceId()); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/v/VindictiveVampire.java b/Mage.Sets/src/mage/cards/v/VindictiveVampire.java index 89c82ab21cff..34af2943e238 100644 --- a/Mage.Sets/src/mage/cards/v/VindictiveVampire.java +++ b/Mage.Sets/src/mage/cards/v/VindictiveVampire.java @@ -57,6 +57,7 @@ class VindictiveVampireTriggeredAbility extends TriggeredAbilityImpl { public VindictiveVampireTriggeredAbility(Zone zone, Effect effect) { super(zone, effect, false); setTriggerPhrase("Whenever another creature you control dies, "); + setLeavesTheBattlefieldTrigger(true); } private VindictiveVampireTriggeredAbility(final VindictiveVampireTriggeredAbility ability) { @@ -69,17 +70,8 @@ public VindictiveVampireTriggeredAbility copy() { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent sourcePermanent; - if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) { - sourcePermanent = game.getPermanent(getSourceId()); - } else { - sourcePermanent = (Permanent) game.getPermanentOrLKIBattlefield(getSourceId()); - } - if (sourcePermanent == null) { - return false; - } - return hasSourceObjectAbility(game, sourcePermanent, event); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } @Override diff --git a/Mage.Sets/src/mage/cards/v/ViridianRevel.java b/Mage.Sets/src/mage/cards/v/ViridianRevel.java index 79212a3654da..9606f89b43e9 100644 --- a/Mage.Sets/src/mage/cards/v/ViridianRevel.java +++ b/Mage.Sets/src/mage/cards/v/ViridianRevel.java @@ -3,6 +3,8 @@ package mage.cards.v; import java.util.UUID; + +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.Card; @@ -40,8 +42,10 @@ public ViridianRevel copy() { } class ViridianRevelTriggeredAbility extends TriggeredAbilityImpl { + ViridianRevelTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), true); + setLeavesTheBattlefieldTrigger(true); } private ViridianRevelTriggeredAbility(final ViridianRevelTriggeredAbility ability) { @@ -75,4 +79,9 @@ public boolean checkTrigger(GameEvent event, Game game) { public String getRule() { return "Whenever an artifact is put into an opponent's graveyard from the battlefield, you may draw a card."; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java b/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java index be011c45707f..74c8976c803f 100644 --- a/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java +++ b/Mage.Sets/src/mage/cards/w/WhispersteelDagger.java @@ -155,10 +155,10 @@ class WhispersteelDaggerWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { if (event.getType() == GameEvent.EventType.SPELL_CAST) { - if (event.getAdditionalReference() == null) { + if (event.getApprovingObject() == null) { return; } - morMap.computeIfAbsent(event.getAdditionalReference().getApprovingMageObjectReference(), m -> new HashMap<>()) + morMap.computeIfAbsent(event.getApprovingObject().getApprovingMageObjectReference(), m -> new HashMap<>()) .computeIfAbsent(game.getOwnerId(event.getSourceId()), m -> new HashMap<>()) .compute(event.getPlayerId(), (u, i) -> i == null ? 0 : Integer.sum(i, -1)); return; diff --git a/Mage.Sets/src/mage/cards/w/Wish.java b/Mage.Sets/src/mage/cards/w/Wish.java index 2c467c3c5f45..dc430487ce8c 100644 --- a/Mage.Sets/src/mage/cards/w/Wish.java +++ b/Mage.Sets/src/mage/cards/w/Wish.java @@ -121,7 +121,7 @@ class WishWatcher extends Watcher { public void watch(GameEvent event, Game game) { if ((GameEvent.EventType.SPELL_CAST.equals(event.getType()) || GameEvent.EventType.LAND_PLAYED.equals(event.getType())) && event.hasApprovingIdentifier(MageIdentifier.WishWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); } } diff --git a/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java b/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java index e892d64e0887..e94568dec23f 100644 --- a/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java +++ b/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java @@ -1,7 +1,9 @@ package mage.cards.x; import mage.MageInt; +import mage.MageObject; import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.common.delayed.WhenTargetDiesDelayedTriggeredAbility; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; @@ -78,6 +80,7 @@ class XiraTheGoldenStingTriggeredAbility extends WhenTargetDiesDelayedTriggeredA super(new DrawCardSourceControllerEffect(1), Duration.Custom, SetTargetPointer.NONE); this.addEffect(new CreateTokenEffect(new XiraBlackInsectToken()).concatBy("and")); setTriggerPhrase("When that creature dies, if it has an egg counter on it, "); + setLeavesTheBattlefieldTrigger(true); } private XiraTheGoldenStingTriggeredAbility(final XiraTheGoldenStingTriggeredAbility ability) { @@ -100,4 +103,9 @@ public boolean isInactive(Game game) { int zccdiff = game.getState().getZoneChangeCounter(mor.getSourceId()) - mor.getZoneChangeCounter(); return zccdiff > 1 || zccdiff > 0 && game.getState().getZone(mor.getSourceId()) != Zone.GRAVEYARD; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java index 540539aa1c90..da8b6749bd84 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ConniveTest.java @@ -239,7 +239,7 @@ public void test_LKI_SpymastersVault_Normal() { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.HAND, playerA, "Grizzly Bears", 1); // - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); addCard(Zone.BATTLEFIELD, playerA, "Razorclaw Bear", 1); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); @@ -267,7 +267,7 @@ public void test_LKI_SpymastersVault_Fizzle() { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.HAND, playerA, "Grizzly Bears", 1); // - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); addCard(Zone.BATTLEFIELD, playerA, "Razorclaw Bear", 1); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); @@ -323,7 +323,7 @@ public void test_LKI_ChangeOfPlans_Fizzle() { // addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); // connive lion castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Change of Plans"); @@ -350,7 +350,7 @@ public void test_LKI_ChangeOfPlans_MultipleTargets() { addCard(Zone.BATTLEFIELD, playerA, "Razorclaw Bear", 1); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); // connive lion castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Change of Plans"); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java index 941fcf4ab14c..a9a210b61567 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ExploitTest.java @@ -61,15 +61,16 @@ public void testExploitTriggerWontGo() { addCard(Zone.HAND, playerB, "Lightning Bolt", 1); addCard(Zone.BATTLEFIELD, playerB, "Thundering Giant"); // 4/3 - setStrictChooseMode(true); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silumgar Butcher"); waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); setChoice(playerA, true); // Choose to exploit setChoice(playerA, "Silvercoat Lion"); // sacrifice to Exploit + // kill butcher before exploit trigger resolve, so no exploits trigger with target + // if you failed here then something wrong with isInUseableZone castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Silumgar Butcher"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java index 8bccbf9446b0..f2e12c3e5e01 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/IncubateTest.java @@ -46,7 +46,7 @@ public void test_Transform_Normal() { @Test public void test_Transform_Custom() { // target transform - addCustomEffect_TransformTarget(playerA); + addCustomEffect_TargetTransform(playerA); // Alluring Suitor, 2/3 // Deadly Dancer, 3/3 @@ -74,7 +74,7 @@ public void test_Transform_Custom() { @Test public void test_Transform_IncubatorToken() { // target transform - addCustomEffect_TransformTarget(playerA); + addCustomEffect_TargetTransform(playerA); // Incubate 3. (Create an Incubator token with three +1/+1 counters on it and “{2}: Transform this artifact.” // It transforms into a 0/0 Phyrexian artifact creature.) @@ -115,9 +115,9 @@ public void test_Transform_CopiedByPermanent_FrontSide() { // use case: copy one side, can't tranform // target transform - addCustomEffect_TransformTarget(playerA); + addCustomEffect_TargetTransform(playerA); // target destroy - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); // Incubate 3. (Create an Incubator token with three +1/+1 counters on it and “{2}: Transform this artifact.” // It transforms into a 0/0 Phyrexian artifact creature.) @@ -169,9 +169,9 @@ public void test_Transform_CopiedByPermanent_BackSide() { // use case: copy one side, can't tranform // target transform - addCustomEffect_TransformTarget(playerA); + addCustomEffect_TargetTransform(playerA); // target destroy - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); // Incubate 3. (Create an Incubator token with three +1/+1 counters on it and “{2}: Transform this artifact.” // It transforms into a 0/0 Phyrexian artifact creature.) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/RecoverTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/RecoverTest.java index 9cd0ae1aa3a3..5984367ae394 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/RecoverTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/RecoverTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; @@ -7,8 +6,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase; /** - * - * @author LevelX2 + * @author LevelX2, JayDi85 */ public class RecoverTest extends CardTestPlayerBase { @@ -20,7 +18,7 @@ public class RecoverTest extends CardTestPlayerBase { * Otherwise, exile this card.” */ @Test - public void testReturnToHand() { + public void test_Normal_ToHand() { addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); // You gain 4 life. // Recover {1}{W} @@ -35,6 +33,7 @@ public void testReturnToHand() { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); setChoice(playerA, true); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -49,7 +48,7 @@ public void testReturnToHand() { } @Test - public void testGoingToExile() { + public void test_Normal_ToExile() { addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); // You gain 4 life. // Recover {1}{W} @@ -64,6 +63,7 @@ public void testGoingToExile() { castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); setChoice(playerA, false); + setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); @@ -74,6 +74,111 @@ public void testGoingToExile() { assertGraveyardCount(playerA, "Silvercoat Lion", 1); assertLife(playerA, 24); + } + + @Test + public void test_DieOther_Single_CanRecover() { + addCustomEffect_TargetDestroy(playerA, 1); + + // Recover—Pay half your life, rounded up. + addCard(Zone.GRAVEYARD, playerA, "Garza's Assassin"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Silvercoat Lion"); + setChoice(playerA, true); // pay half life + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, "Garza's Assassin", 1); // after recover + assertLife(playerA, 20 / 2); + } + + @Test + public void test_DieOther_Multiple_CanRecover() { + // ruling from wiki: + // If multiple creatures are put into your graveyard from the battlefield at the same time, the recover + // ability of a card already in your graveyard triggers that many times. Only the first one to resolve + // will cause the card to move somewhere. By the time any of the other triggers resolve, the card won't be + // in your graveyard anymore. You can still pay the recover cost, but nothing else will happen. + + addCustomEffect_TargetDestroy(playerA, 2); + + // Recover—Pay half your life, rounded up. + addCard(Zone.GRAVEYARD, playerA, "Garza's Assassin"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + + // raise 2 recover triggers, pay second trigger - it will be fizzled + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Silvercoat Lion^Grizzly Bears"); + setChoice(playerA, "Recover—Pay half your life"); // x2 triggers order + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, 1); + checkStackObject("on recover triggers", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Recover—Pay half your life", 2); + setChoice(playerA, false); // first trigger resolve - do not pay and exile + setChoice(playerA, true); // second trigger resolve - pay and fizzle + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, "Garza's Assassin", 1); // after first unpayed trigger + assertLife(playerA, 20 / 2); // after second unpayed trigger + } + @Test + public void test_DieItself_MustNotWork() { + // ruling from wiki: + // If a creature with recover is put into your graveyard from the battlefield, it doesn't cause its + // own recover ability to trigger. Similarly, if another creature is put into your graveyard from + // the battlefield at the same time that a card with recover is put there, it won't cause that + // recover ability to trigger. + + addCustomEffect_TargetDestroy(playerA, 1); + + // Recover—Pay half your life, rounded up. + addCard(Zone.BATTLEFIELD, playerA, "Garza's Assassin"); + + // no recover + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Garza's Assassin"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Garza's Assassin", 1); + assertLife(playerA, 20); + } + + @Test + public void test_DieItselfAndMultiple_MustNotWork() { + // ruling from wiki: + // If a creature with recover is put into your graveyard from the battlefield, it doesn't cause its + // own recover ability to trigger. Similarly, if another creature is put into your graveyard from + // the battlefield at the same time that a card with recover is put there, it won't cause that + // recover ability to trigger. + + // reason: it's leaves-the-battlefield trigger and look back in time (source was on battlefield in that time, so no trigger) + + addCustomEffect_TargetDestroy(playerA, 2); + + // Recover—Pay half your life, rounded up. + addCard(Zone.BATTLEFIELD, playerA, "Garza's Assassin"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); + + // no recover (if you catch recover dialog then something wrong with isInUseableZone) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Garza's Assassin^Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Garza's Assassin", 1); + assertGraveyardCount(playerA, "Silvercoat Lion", 1); + assertLife(playerA, 20); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java index 3b066bf21451..75ef87401b09 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/PhantasmalImageTest.java @@ -668,4 +668,66 @@ public void testAnimatedKindred() { assertTrue("Cloak and Dagger should be a Rogue", cloakB.hasSubtype(SubType.ROGUE, currentGame)); assertTrue("Cloak and Dagger should be an Equipment", cloakB.hasSubtype(SubType.EQUIPMENT, currentGame)); } + + @Test + public void test_SelfExploit_SidisiUndeadVizier_Normal() { + // bug https://github.com/magefree/mage/issues/5925 + // You may have Phantasmal Image enter the battlefield as a copy of any creature on the battlefield, + // except it's an Illusion in addition to its other types and it has "When this creature becomes the + // target of a spell or ability, sacrifice it." + addCard(Zone.HAND, playerA, "Phantasmal Image"); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + // + // Exploit (When this creature enters the battlefield, you may sacrifice a creature.) + // When Sidisi, Undead Vizier exploits a creature, you may search your library for a card, put it into your hand, then shuffle your library. + addCard(Zone.BATTLEFIELD, playerA, "Sidisi, Undead Vizier"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy + setChoice(playerA, "Sidisi, Undead Vizier"); // to copy + setChoice(playerA, "Sidisi, Undead Vizier[only copy]"); // legendary rule, keep copy + setChoice(playerA, true); // use exploit on etb + setChoice(playerA, "Sidisi, Undead Vizier[only copy]"); // sacrifice itself on exploit + setChoice(playerA, true); // use exploit trigger (search lib) + addTarget(playerA, "Mountain"); // tutor mountain + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, "Mountain", 1); + } + + @Test + public void test_SelfExploit_SidisiUndeadVizier_Exile() { + // exploit look for sacrifice only, not a dies conditional - so it must work with exile replace + + // You may have Phantasmal Image enter the battlefield as a copy of any creature on the battlefield, + // except it's an Illusion in addition to its other types and it has "When this creature becomes the + // target of a spell or ability, sacrifice it." + addCard(Zone.HAND, playerA, "Phantasmal Image"); // {1}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + // + // Exploit (When this creature enters the battlefield, you may sacrifice a creature.) + // When Sidisi, Undead Vizier exploits a creature, you may search your library for a card, put it into your hand, then shuffle your library. + addCard(Zone.BATTLEFIELD, playerA, "Sidisi, Undead Vizier"); + // + // If a card or token would be put into a graveyard from anywhere, exile it instead. + addCard(Zone.BATTLEFIELD, playerA, "Rest in Peace"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); // use copy + setChoice(playerA, "Sidisi, Undead Vizier"); // to copy + setChoice(playerA, "Sidisi, Undead Vizier[only copy]"); // legendary rule, keep copy + setChoice(playerA, true); // use exploit on etb + setChoice(playerA, "Sidisi, Undead Vizier[only copy]"); // sacrifice itself on exploit + setChoice(playerA, true); // use exploit trigger (search lib) + addTarget(playerA, "Mountain"); // tutor mountain + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertHandCount(playerA, "Mountain", 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java index fbc6887c7643..cd1c006c3d10 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/ZoneChangeReplacementTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.replacement; import mage.constants.PhaseStep; @@ -384,4 +383,4 @@ public void testAnafenzaExileInCombat() { assertPermanentCount(playerA, "Anafenza, the Foremost", 0); assertGraveyardCount(playerA, "Anafenza, the Foremost", 1); } -} +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java index 555d9793b022..fe28717b08c4 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c18/GeodeGolemTest.java @@ -136,7 +136,7 @@ public void test_MDF_BothSides() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10); // addCustomEffect_TargetDamage(playerA, 3); - addCustomEffect_DestroyTarget(playerA); + addCustomEffect_TargetDestroy(playerA); checkCommandCardCount("before 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Birgi, God of Storytelling", 1); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/VerrakWarpedSengirTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/VerrakWarpedSengirTest.java index 1df34463bf43..50823474d0fe 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/VerrakWarpedSengirTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/dmc/VerrakWarpedSengirTest.java @@ -116,4 +116,25 @@ public void test_CopyOnPayLife_PaymentModification() { assertLife(playerA, 20 - 2 * 2 + 2 * 2); // x2 pays, x2 gains assertLife(playerB, 20 - 2 * 2); // x2 lose } + + @Test + public void test_MustNotTriggerOnDiscardCost() { + // bug: https://github.com/magefree/mage/issues/12089 + + // Whenever you activate an ability that isn’t a mana ability, if life was paid to activate it, + // you may pay that much life again. If you do, copy that ability. You may choose new targets for the copy. + addCard(Zone.BATTLEFIELD, playerA, "Verrak, Warped Sengir"); + // + // Pay 2 life, Sacrifice another creature: Search your library for a card, put that card into your hand, then shuffle. + addCard(Zone.BATTLEFIELD, playerA, "Razaketh, the Foulblooded"); + + // activate without copy trigger (discard cost pay will remove Verrak before activate the ability) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pay 2 life, Sacrifice"); + setChoice(playerA, "Verrak, Warped Sengir"); // sacrifice cost + addTarget(playerA, "Mountain"); // search lib + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + setStrictChooseMode(true); + execute(); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AjanisLastStandTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AjanisLastStandTest.java new file mode 100644 index 000000000000..8fa520e87af2 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AjanisLastStandTest.java @@ -0,0 +1,57 @@ +package org.mage.test.cards.single.m19; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class AjanisLastStandTest extends CardTestPlayerBase { + + @Test + public void test_TriggerOnAlive() { + addCustomEffect_TargetDestroy(playerA); + + // Whenever a creature or planeswalker you control dies, you may sacrifice Ajani's Last Stand. + // If you do, create a 4/4 white Avatar creature token with flying. + addCard(Zone.BATTLEFIELD, playerA, "Ajani's Last Stand"); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy"); + addTarget(playerA, "Grizzly Bears"); // to destroy + setChoice(playerA, true); // yes to sacrifice + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Ajani's Last Stand", 1); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertTokenCount(playerA, "Avatar Token", 1); + } + + @Test + public void test_NoTriggerOnSelfDies() { + addCustomEffect_AllDestroy(playerA); + + // Whenever a creature or planeswalker you control dies, you may sacrifice Ajani's Last Stand. + // If you do, create a 4/4 white Avatar creature token with flying. + addCard(Zone.BATTLEFIELD, playerA, "Ajani's Last Stand"); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + + // destroy all without triggers (cause no sacrifice cost can be pay here) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "all destroy"); + + setStopAt(1, PhaseStep.END_TURN); + setStrictChooseMode(true); + execute(); + + assertGraveyardCount(playerA, "Ajani's Last Stand", 1); + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertTokenCount(playerA, "Avatar Token", 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/GluttonousHellkiteTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/GluttonousHellkiteTest.java index 9558b74aeccf..4c3b4dca6bb9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/GluttonousHellkiteTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/GluttonousHellkiteTest.java @@ -112,7 +112,7 @@ public void test_CastWithSac_LackingToSac() { @Test public void test_CastWithSac_SacFullAndBlink() { - addCustomEffect_BlinkTarget(playerA); + addCustomEffect_TargetBlink(playerA); // When you cast this spell, each player sacrifices X creatures. Gluttonous Hellkite enters the battlefield // with two +1/+1 counters on it for each creature sacrificed this way. diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ChronozoaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ChronozoaTest.java index 4949f349a9cb..19ebc89e3806 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ChronozoaTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ChronozoaTest.java @@ -23,7 +23,7 @@ public void testTriggerOtherCreature() { addCard(Zone.BATTLEFIELD, playerA, "Island", 4); // Flying // Vanishing 3 (This permanent enters the battlefield with three time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.) - // When Chronozoa is put into a graveyard from play, if it had no time counters on it, create two tokens that are copies of it. + // When Chronozoa dies, if it had no time counters on it, create two tokens that are copies of it. addCard(Zone.HAND, playerA, "Chronozoa"); // {3}{U} addCard(Zone.GRAVEYARD, playerA, "Chronozoa"); // Sacrifice a creature: Scry 1. (To scry 1, look at the top card of your library, then you may put that card on the bottom of your library.) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SacrificeDiesTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SacrificeDiesTriggerTest.java index dd6088f56ff6..eec48b69efaf 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SacrificeDiesTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SacrificeDiesTriggerTest.java @@ -314,7 +314,7 @@ public void test_SuChi_SageOfLatNam_10OtherTriggers() { @Test public void test_MultiModesDiesTrigger_ByDamage() { - addCustomEffect_BlinkTarget(playerA); + addCustomEffect_TargetBlink(playerA); // When Junji, the Midnight Sky dies, choose one — // • Each opponent discards two cards and loses 2 life. @@ -346,7 +346,7 @@ public void test_MultiModesDiesTrigger_ByDamage() { @Test public void test_MultiModesDiesTrigger_BySacrificeCost() { - addCustomEffect_BlinkTarget(playerA); + addCustomEffect_TargetBlink(playerA); // When Junji, the Midnight Sky dies, choose one — // • Each opponent discards two cards and loses 2 life. diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SeerOfStolenSightTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SeerOfStolenSightTest.java new file mode 100644 index 000000000000..8a0fee2735e3 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SeerOfStolenSightTest.java @@ -0,0 +1,81 @@ +package org.mage.test.cards.triggers.dies; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ + +public class SeerOfStolenSightTest extends CardTestPlayerBase { + + @Test + public void test_SingleDie() { + skipInitShuffling(); + addCustomEffect_TargetDestroy(playerA); + + // Whenever one or more artifacts and/or creatures you control are put into a graveyard from the battlefield, surveil 1. + addCard(Zone.BATTLEFIELD, playerA, "Seer of Stolen Sight"); + addCard(Zone.LIBRARY, playerA, "Swamp", 2); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Grizzly Bears"); + addTarget(playerA, "Swamp"); // surveil + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertGraveyardCount(playerA, "Swamp", 1); + } + + @Test + public void test_MultipleDies() { + skipInitShuffling(); + addCustomEffect_TargetDestroy(playerA, 2); + + // Whenever one or more artifacts and/or creatures you control are put into a graveyard from the battlefield, surveil 1. + addCard(Zone.BATTLEFIELD, playerA, "Seer of Stolen Sight"); + addCard(Zone.LIBRARY, playerA, "Swamp", 2); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 2); + + // must catch only one time trigger + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Grizzly Bears^Grizzly Bears"); + addTarget(playerA, "Swamp"); // surveil + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Grizzly Bears", 2); + assertGraveyardCount(playerA, "Swamp", 1); + } + + @Test + public void test_OwnDie() { + skipInitShuffling(); + addCustomEffect_TargetDestroy(playerA); + + // Whenever one or more artifacts and/or creatures you control are put into a graveyard from the battlefield, surveil 1. + addCard(Zone.BATTLEFIELD, playerA, "Seer of Stolen Sight"); + addCard(Zone.LIBRARY, playerA, "Swamp", 2); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 1); + + // must trigger on own die + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "target destroy", "Seer of Stolen Sight"); + addTarget(playerA, "Swamp"); // surveil + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Seer of Stolen Sight", 1); + assertGraveyardCount(playerA, "Swamp", 1); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java index 0cb9e99549e8..9f63da3acc70 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/VengefulTownsfolkTest.java @@ -8,7 +8,7 @@ /** * Tests the {@link mage.abilities.common.DiesOneOrMoreTriggeredAbility} batching. * - * @author Susucr + * @author Susucr, JayDi85 */ public class VengefulTownsfolkTest extends CardTestPlayerBase { @@ -145,4 +145,52 @@ public void testDoubleSacrifice() { assertPermanentCount(playerA, townsfolk, 1); assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1); } + + @Test + public void testReplacedDieEvent_Single() { + addCard(Zone.BATTLEFIELD, playerA, townsfolk); + // + // {T}, Sacrifice up to three permanents: If there were three or more card types among the sacrificed permanents, each opponent loses 3 life, you gain 3 life, and you draw three cards. + addCard(Zone.BATTLEFIELD, playerA, "Baba Lysaga, Night Witch"); + addCard(Zone.BATTLEFIELD, playerA, "Angel of the God-Pharaoh"); // creature with cycle (will be exiled instead die) + // + // If a card with cycling would be put into your graveyard from anywhere and it wasn't cycled, exile it instead. + addCard(Zone.BATTLEFIELD, playerA, "Abandoned Sarcophagus"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice up to three"); + setChoice(playerA, "Angel of the God-Pharaoh"); // sac cost + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, "Angel of the God-Pharaoh", 1); // exiled instead die + assertPermanentCount(playerA, townsfolk, 1); + assertPowerToughness(playerA, townsfolk, 3, 3); // no triggers + } + + @Test + public void testReplacedDieEvent_Multiple() { + addCard(Zone.BATTLEFIELD, playerA, townsfolk); + // + // {T}, Sacrifice up to three permanents: If there were three or more card types among the sacrificed permanents, each opponent loses 3 life, you gain 3 life, and you draw three cards. + addCard(Zone.BATTLEFIELD, playerA, "Baba Lysaga, Night Witch"); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 10); // creature + addCard(Zone.BATTLEFIELD, playerA, "Angel of the God-Pharaoh"); // creature with cycle (will be exiled instead die) + // + // If a card with cycling would be put into your graveyard from anywhere and it wasn't cycled, exile it instead. + addCard(Zone.BATTLEFIELD, playerA, "Abandoned Sarcophagus"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Sacrifice up to three"); + setChoice(playerA, "Grizzly Bears^Angel of the God-Pharaoh"); // sac cost + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Grizzly Bears", 1); + assertExileCount(playerA, "Angel of the God-Pharaoh", 1); // exiled instead die + assertPermanentCount(playerA, townsfolk, 1); + assertPowerToughness(playerA, townsfolk, 3 + 1, 3 + 1); + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacmentTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacementTest.java similarity index 96% rename from Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacmentTest.java rename to Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacementTest.java index 490f33bb0a20..418673e9d129 100644 --- a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacmentTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderManaReplacementTest.java @@ -13,7 +13,7 @@ * * @author LevelX2 */ -public class CommanderManaReplacmentTest extends CardTestCommanderDuelBase { +public class CommanderManaReplacementTest extends CardTestCommanderDuelBase { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index ca7e6c859e87..4b9deb86cf98 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java @@ -318,9 +318,6 @@ protected void addCustomCardWithAbility(String customName, TestPlayer controller /** * Add cost modification effect to the game (all cast cost will be increaded or decreased for controller) - * - * @param controller - * @param modificationAmount */ protected void addCustomEffect_SpellCostModification(TestPlayer controller, int modificationAmount) { Effect effect; @@ -339,9 +336,6 @@ protected void addCustomEffect_SpellCostModification(TestPlayer controller, int /** * Add target damage ability that can be called by text: "target damage xxx" - * - * @param controller - * @param damageAmount */ protected void addCustomEffect_TargetDamage(TestPlayer controller, int damageAmount) { Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(damageAmount).setText("target damage " + damageAmount), new ManaCostsImpl<>("")); @@ -355,12 +349,17 @@ protected void addCustomEffect_TargetDamage(TestPlayer controller, int damageAmo /** * Add target destroy ability that can be called by text "target destroy" - * - * @param controller */ - protected void addCustomEffect_DestroyTarget(TestPlayer controller) { + protected void addCustomEffect_TargetDestroy(TestPlayer controller) { + addCustomEffect_TargetDestroy(controller, 1); + } + + /** + * Add target destroy ability that can be called by text "target destroy" + */ + protected void addCustomEffect_TargetDestroy(TestPlayer controller, int numberOfTargets) { Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect().setText("target destroy"), new ManaCostsImpl<>("")); - ability.addTarget(new TargetPermanent()); + ability.addTarget(new TargetPermanent(numberOfTargets, StaticFilters.FILTER_PERMANENT)); addCustomCardWithAbility( "target destroy for " + controller.getName(), controller, @@ -368,12 +367,22 @@ protected void addCustomEffect_DestroyTarget(TestPlayer controller) { ); } + /** + * Add all destroy ability that can be called by text "all destroy" + */ + protected void addCustomEffect_AllDestroy(TestPlayer controller) { + Ability ability = new SimpleActivatedAbility(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT).setText("all destroy"), new ManaCostsImpl<>("")); + addCustomCardWithAbility( + "all destroy for " + controller.getName(), + controller, + ability + ); + } + /** * Add target transform ability that can be called by text "target transform" - * - * @param controller */ - protected void addCustomEffect_TransformTarget(TestPlayer controller) { + protected void addCustomEffect_TargetTransform(TestPlayer controller) { Ability ability = new SimpleActivatedAbility(new TransformTargetEffect().setText("target transform"), new ManaCostsImpl<>("")); ability.addTarget(new TargetPermanent()); addCustomCardWithAbility( @@ -385,10 +394,8 @@ protected void addCustomEffect_TransformTarget(TestPlayer controller) { /** * Add target blink ability that can be called by text "target blink" - * - * @param controller */ - protected void addCustomEffect_BlinkTarget(TestPlayer controller) { + protected void addCustomEffect_TargetBlink(TestPlayer controller) { Ability ability = new SimpleActivatedAbility( new ExileThenReturnTargetEffect(true, true).setText("target blink"), new ManaCostsImpl<>("") @@ -403,8 +410,6 @@ protected void addCustomEffect_BlinkTarget(TestPlayer controller) { /** * Return target card to hand that can be called by text "return from ..." - * - * @param controller */ protected void addCustomEffect_ReturnFromAnyToHand(TestPlayer controller) { // graveyard diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 064801a98cfb..e018ec86f553 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -7,6 +7,7 @@ import mage.abilities.Ability; import mage.abilities.AbilityImpl; import mage.abilities.Mode; +import mage.abilities.TriggeredAbility; import mage.abilities.common.*; import mage.abilities.condition.Condition; import mage.abilities.costs.Cost; @@ -1979,10 +1980,58 @@ private void checkMissingAbilities(Card card, MtgJsonCard ref) { fail(card, "abilities", "legendary nonpermanent cards need to have LegendarySpellAbility"); } + // special check: mutate is not supported yet, so must be removed from sets if (card.getAbilities().containsClass(MutateAbility.class)) { fail(card, "abilities", "mutate cards aren't implemented and shouldn't be available"); } + // special check: wrong dies triggers (there are also a runtime check on wrong usage, see isInUseableZoneDiesTrigger) + Set ignoredCards = new HashSet<>(); + ignoredCards.add("Caller of the Claw"); + ignoredCards.add("Boneyard Scourge"); + ignoredCards.add("Fell Shepherd"); + ignoredCards.add("Massacre Girl"); + ignoredCards.add("Infested Thrinax"); + ignoredCards.add("Xira, the Golden Sting"); + ignoredCards.add("Mawloc"); + List ignoredAbilities = new ArrayList<>(); + ignoredAbilities.add("roll"); // roll die effects + ignoredAbilities.add("with \"When"); // token creating effects + ignoredAbilities.add("gains \"When"); // token creating effects + ignoredAbilities.add("and \"When"); // token creating effects + ignoredAbilities.add("it has \"When"); // token creating effects + ignoredAbilities.add("beginning of your end step"); // step triggers + ignoredAbilities.add("beginning of each end step"); // step triggers + ignoredAbilities.add("beginning of combat"); // step triggers + if (!ignoredCards.contains(card.getName())) { + for (Ability ability : card.getAbilities()) { + TriggeredAbility triggeredAbility = ability instanceof TriggeredAbility ? (TriggeredAbility) ability : null; + if (triggeredAbility == null) { + continue; + } + // search and check dies related abilities + String rules = triggeredAbility.getRule(); + if (ignoredAbilities.stream().anyMatch(rules::contains)) { + continue; + } + boolean isDiesAbility = rules.contains("die ") + || rules.contains("dies ") + || rules.contains("die,") + || rules.contains("dies,"); + boolean isPutToGraveAbility = rules.contains("put into") + && rules.contains("graveyard") + && rules.contains("from the battlefield"); + if (triggeredAbility.isLeavesTheBattlefieldTrigger()) { + // TODO: add check for wrongly enabled settings too? + } else { + if (isDiesAbility || isPutToGraveAbility) { + fail(card, "abilities", "dies related trigger must use setLeavesTheBattlefieldTrigger(true) and possibly override isInUseableZone - " + + triggeredAbility.getClass().getSimpleName()); + } + } + } + } + // special check: duplicated words in ability text (wrong target/filter usage) // example: You may exile __two two__ blue cards // possible fixes: @@ -2303,6 +2352,9 @@ private static boolean checkForEffect(Card card, Class effectC } private void checkWrongAbilitiesTextStart() { + if (FULL_ABILITIES_CHECK_SET_CODES.isEmpty()) { + return; + } System.out.println("Ability text checks started for " + FULL_ABILITIES_CHECK_SET_CODES); wrongAbilityStatsTotal = 0; wrongAbilityStatsGood = 0; @@ -2310,6 +2362,10 @@ private void checkWrongAbilitiesTextStart() { } private void checkWrongAbilitiesTextEnd() { + if (FULL_ABILITIES_CHECK_SET_CODES.isEmpty()) { + return; + } + // TODO: implement tests result/stats by github actions to show in check message compared to prev version System.out.println(); System.out.printf("Stats for %d cards checked for abilities text:%n", wrongAbilityStatsTotal); diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index ab458969a709..96f1a1941956 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -86,9 +86,6 @@ public interface Ability extends Controllable, Serializable { * Gets the id of the object which put this ability in motion. *

* WARNING, MageSingleton abilities contains dirty data here, so you can't use sourceId with it - * - * @return The {@link java.util.UUID} of the object this ability is - * associated with. */ UUID getSourceId(); @@ -351,16 +348,24 @@ default boolean activate(Game game, boolean noMana) { void addWatcher(Watcher watcher); /** - * Returns true if this abilities source is in the zone for the ability + * Allow to control ability/trigger's lifecycle + *

+ * How-to use: + * - for normal abilities and triggers - keep default + * - for leave battlefield triggers - keep default + set setLeavesTheBattlefieldTrigger(true) + * - for dies triggers - override and use TriggeredAbilityImpl.isInUseableZoneDiesTrigger inside + set setLeavesTheBattlefieldTrigger(true) + * + * @param sourceObject can be null for static continues effects checking like rules modification (example: Yixlid Jailer) + * @param event can be null for state base effects checking like "when you control seven or more" (example: Endrek Sahr, Master Breeder) */ - boolean isInUseableZone(Game game, MageObject source, GameEvent event); + boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event); /** * Returns true if the source object has currently the ability (e.g. The * object can have lost all or some abilities for some time (e.g. Turn to * Frog) */ - boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event); + boolean hasSourceObjectAbility(Game game, MageObject sourceObject, GameEvent event); /** * Returns true if the ability has a tap itself in their costs @@ -478,6 +483,7 @@ default boolean hasTapCost() { /** * Finds the source object regardless of its zcc. Can be LKI from battlefield in some cases. + * Warning, do not use with singleton abilities */ MageObject getSourceObject(Game game); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 0830467cbdb8..214b7b015a9c 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -29,7 +29,9 @@ import mage.game.command.Dungeon; import mage.game.command.Emblem; import mage.game.command.Plane; +import mage.game.events.BatchEvent; import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.game.stack.Spell; import mage.game.stack.StackAbility; @@ -1172,69 +1174,145 @@ protected static boolean canChooseTargetAbility(Ability ability, Modes modes, Ga return false; } - /** - * @param game - * @param source - * @return - */ @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - if (!this.hasSourceObjectAbility(game, source, event)) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (!this.hasSourceObjectAbility(game, sourceObject, event)) { return false; } + + // workaround for singleton abilities like Flying + UUID affectedSourceId = getRealSourceObjectId(this, sourceObject); + + // in command zone if (zone == Zone.COMMAND) { - if (this.getSourceId() == null) { // commander effects + if (affectedSourceId == null) { + // commander effects return true; + } else { + MageObject object = game.getObject(affectedSourceId); + // emblem/planes are always actual + if (object instanceof Emblem || object instanceof Dungeon || object instanceof Plane) { + return true; + } } - MageObject object = game.getObject(this.getSourceId()); - // emblem/planes are always actual - if (object instanceof Emblem || object instanceof Dungeon || object instanceof Plane) { - return true; + } + + // on entering permanents - must use static abilities like it already on battlefield + // example: Tatterkite enters without counters from Mikaeus, the Unhallowed + if (game.getPermanentEntering(affectedSourceId) != null && zone == Zone.BATTLEFIELD) { + return true; + } + + // 603.10. + // Normally, objects that exist immediately after an event are checked to see if the event matched + // any trigger conditions, and continuous effects that exist at that time are used to determine what the + // trigger conditions are and what the objects involved in the event look like. + // ... + Zone sourceObjectZone = game.getState().getZone(affectedSourceId); + + // 603.10. + // ... + // However, some triggered abilities are exceptions to this rule; the game “looks back in time” to determine + // if those abilities trigger, using the existence of those abilities and the appearance of objects + // immediately prior to the event. The list of exceptions is as follows: + + // 603.10a + // Some zone-change triggers look back in time. These are leaves-the-battlefield abilities, + // abilities that trigger when a card leaves a graveyard, and abilities that trigger when an object that all + // players can see is put into a hand or library. + // TODO: research "leaves a graveyard" + // TODO: research "put into a hand or library" + if (isTriggerCanFireAfterLeaveBattlefield(event)) { + // permanents with normal triggers + if (sourceObject instanceof Permanent) { // TODO: use affectedSourceObject here? + // support leaves-the-battlefield abilities + sourceObjectZone = Zone.BATTLEFIELD; } + // permanents with continues effects like Yixlid Jailer, see related code "isInUseableZone(game, null" + if (sourceObject == null && this instanceof StaticAbility) { + sourceObjectZone = Zone.BATTLEFIELD; + } + } + + // TODO: research use cases and implement shared logic with "looking zone" instead LKI only + // 603.10b Abilities that trigger when a permanent phases out look back in time. + // 603.10c Abilities that trigger specifically when an object becomes unattached look back in time. + // 603.10d Abilities that trigger when a player loses control of an object look back in time. + // 603.10e Abilities that trigger when a spell is countered look back in time. + // 603.10f Abilities that trigger when a player loses the game look back in time. + // 603.10g Abilities that trigger when a player planeswalks away from a plane look back in time. + + return zone.match(sourceObjectZone); + } + + public static boolean isTriggerCanFireAfterLeaveBattlefield(GameEvent event) { + if (event == null) { + return false; } - UUID parameterSourceId; - // for singleton abilities like Flying we can't rely on abilities' source because it's only once in continuous effects + List allEvents = new ArrayList<>(); + if (event instanceof BatchEvent) { + allEvents.addAll(((BatchEvent) event).getEvents()); + } else { + allEvents.add(event); + } + + return allEvents.stream().anyMatch(e -> { + // TODO: need sync code with TriggeredAbilityImpl.isInUseableZone + // TODO: add more events with zone change logic (or make it event's param)? + // need research: is it ability's or event's task? + // - ability's task: code like ability.setLookBackInTime + // - event's task: code like current switch + // TODO: alternative solution: replace check by source.isLeavesTheBattlefieldTrigger? + switch (e.getType()) { + case DESTROYED_PERMANENT: + case EXPLOITED_CREATURE: + return true; + case ZONE_CHANGE: + return ((ZoneChangeEvent) e).getFromZone() == Zone.BATTLEFIELD; + default: + return false; + } + }); + } + + /** + * Find real source object id from any ability (real and singleton) + */ + protected static UUID getRealSourceObjectId(Ability sourceAbility, MageObject sourceObject) { + // In singleton abilities like Flying we can't rely on ability's source because it's init only once in continuous effects // so will use the sourceId of the object itself that came as a parameter if it is not null - if (this instanceof MageSingleton && source != null) { - parameterSourceId = source.getId(); + if (sourceAbility instanceof MageSingleton && sourceObject != null) { + return sourceObject.getId(); } else { - parameterSourceId = getSourceId(); - } - // check against shortLKI for effects that move multiple object at the same time (e.g. destroy all) - if (game.checkShortLivingLKI(getSourceId(), getZone())) { - return true; + return sourceAbility.getSourceId(); } - // check against current state - Zone test = game.getState().getZone(parameterSourceId); - return zone.match(test); } @Override - public boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event) { - // if source object have this ability - // uses for ability.isInUseableZone - // replacement and other continues effects can be without source, but active (must return true) - - MageObject object = source; - // for singleton abilities like Flying we can't rely on abilities' source because it's only once in continuous effects - // so will use the sourceId of the object itself that came as a parameter if it is not null + public final boolean hasSourceObjectAbility(Game game, MageObject sourceObject, GameEvent event) { + MageObject object = sourceObject; if (object == null) { object = game.getPermanentEntering(getSourceId()); if (object == null) { object = game.getObject(getSourceId()); } } - if (object != null) { - if (object instanceof Permanent) { - return object.hasAbility(this, game) && ( - ((Permanent) object).isPhasedIn() || this.getWorksPhasedOut() - ); - } else { - // cards and other objects - return object.hasAbility(this, game); - } + + if (object == null) { + // replacement and other continues effects can be without source, but active (must return true all time) + return true; } + + if (!object.hasAbility(this, game)) { + return false; + } + + // phase in/out support + if (object instanceof Permanent) { + return ((Permanent) object).isPhasedIn() || this.getWorksPhasedOut(); + } + return true; } diff --git a/Mage/src/main/java/mage/abilities/StaticAbility.java b/Mage/src/main/java/mage/abilities/StaticAbility.java index f826eff405f6..3e00c3ca6de8 100644 --- a/Mage/src/main/java/mage/abilities/StaticAbility.java +++ b/Mage/src/main/java/mage/abilities/StaticAbility.java @@ -1,12 +1,8 @@ - package mage.abilities; -import mage.MageObject; import mage.abilities.effects.Effect; import mage.constants.AbilityType; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; /** * @@ -25,17 +21,6 @@ public StaticAbility(Zone zone, Effect effect) { } } - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - if (game.checkShortLivingLKI(getSourceId(), zone)) { - return true; // maybe this can be a problem if effects removed the ability from the object - } - if (game.getPermanentEntering(getSourceId()) != null && zone == Zone.BATTLEFIELD) { - return true; // abilities of permanents entering battlefield are countes as on battlefield - } - return super.isInUseableZone(game, source, event); - } - protected StaticAbility(final StaticAbility ability) { super(ability); } diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java index 9d8ab33cef6e..3670dc461f65 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java @@ -209,7 +209,7 @@ private void checkTrigger(TriggeredAbility ability, GameEvent event, Game game) if (ability.isInUseableZone(game, object, event)) { if (event == null || !game.getContinuousEffects().preventedByRuleModification(event, ability, game, false)) { if (object != null) { - boolean controllerSet = false; + boolean controllerSet = false; // TODO: wtf?!?!? Need rework whole "set" logic here Set eventTargets = CardUtil.getEventTargets(event); if (ability.getZone() != Zone.COMMAND && event != null diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbility.java b/Mage/src/main/java/mage/abilities/TriggeredAbility.java index 7d690c6a8620..f47a10f76c6b 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbility.java @@ -62,8 +62,26 @@ public interface TriggeredAbility extends Ability { TriggeredAbility setOptional(); + /** + * Allow trigger to fire after source leave the battlefield (example: will use LKI on itself sacrifice) + */ boolean isLeavesTheBattlefieldTrigger(); + /** + * 603.6c,603.6d + * If true the game “looks back in time” to determine if those abilities trigger + * This has to be set, if the triggered ability has to check back in time if the permanent the ability is connected + * to had the ability on the battlefield while the trigger is checked + *

+ * 603.6c + * Leaves-the-battlefield abilities trigger when a permanent moves from the battlefield to another zone, + * or when a phased-in permanent leaves the game because its owner leaves the game. These are written as, + * but aren’t limited to, “When [this object] leaves the battlefield, . . .” or “Whenever [something] is put + * into a graveyard from the battlefield, . . . .” (See also rule 603.10.) An ability that attempts to do + * something to the card that left the battlefield checks for it only in the first zone that it went to. + * An ability that triggers when a card is put into a certain zone “from anywhere” is never treated as a + * leaves-the-battlefield ability, even if an object is put into that zone from the battlefield. + */ void setLeavesTheBattlefieldTrigger(boolean leavesTheBattlefieldTrigger); @Override diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index 29030e2bf3fa..a119c31c1bbe 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -8,7 +8,9 @@ import mage.constants.AbilityWord; import mage.constants.Zone; import mage.game.Game; +import mage.game.events.BatchEvent; import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeBatchEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentToken; @@ -49,7 +51,6 @@ protected TriggeredAbilityImpl(Zone zone, Effect effect, boolean optional) { // verify check: DoIfCostPaid effect already asks about action (optional), so no needs to ask it again in triggered ability if (effect instanceof DoIfCostPaid && (this.optional && ((DoIfCostPaid) effect).isOptional())) { throw new IllegalArgumentException("DoIfCostPaid effect must have only one optional settings, but it have two (trigger + DoIfCostPaid): " + this.getClass().getSimpleName()); - } } @@ -347,84 +348,73 @@ protected final String getWhen() { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - - /** - * 603.6. Trigger events that involve objects changing zones are called - * “zone-change triggers.” Many abilities with zone-change triggers - * attempt to do something to that object after it changes zones. During - * resolution, these abilities look for the object in the zone that it - * moved to. If the object is unable to be found in the zone it went to, - * the part of the ability attempting to do something to the object will - * fail to do anything. The ability could be unable to find the object - * because the object never entered the specified zone, because it left - * the zone before the ability resolved, or because it is in a zone that - * is hidden from a player, such as a library or an opponent's hand. - * (This rule applies even if the object leaves the zone and returns - * again before the ability resolves.) The most common zone-change - * triggers are enters-the-battlefield triggers and - * leaves-the-battlefield triggers. - * - * from: - * http://www.mtgsalvation.com/forums/magic-fundamentals/magic-rulings/magic-rulings-archives/537065-ixidron-and-kozilek - * There are two types of triggers that involve the graveyard: dies - * triggers (which are a subset of leave-the-battlefield triggers) and - * put into the graveyard from anywhere triggers. - * - * The former triggers trigger based on the game state prior to the move - * where the Kozilek permanent is face down and has no abilities. The - * latter triggers trigger from the game state after the move where the - * Kozilek card is itself and has the ability. - */ - - Set eventTargets = CardUtil.getEventTargets(event); - if (!eventTargets.contains(getSourceId())) { - return super.isInUseableZone(game, source, event); - } - - switch (event.getType()) { - case ZONE_CHANGE: - ZoneChangeEvent zce = (ZoneChangeEvent) event; - if (eventTargets.contains(getSourceId()) && !zce.getToZone().isPublicZone()) { - // If an ability triggers when the object that has it is put into a hidden zone from a graveyard, - // that ability triggers from the graveyard, (such as Golgari Brownscale), - // Yixlid Jailer will prevent that ability from triggering. - if (zce.getFromZone().match(Zone.GRAVEYARD)) { - if (!CardUtil.cardHadAbility(this, game.getLastKnownInformationCard(getSourceId(), zce.getFromZone()), getSourceId(), game)) { - return false; + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + + // workaround for singleton abilities like Flying + UUID affectedSourceId = getRealSourceObjectId(this, sourceObject); + + // 603.6 + // Trigger events that involve objects changing zones are called "zone-change triggers." Many abilities with + // zone-change triggers attempt to do something to that object after it changes zones. During resolution, + // these abilities look for the object in the zone that it moved to. If the object is unable to be found + // in the zone it went to, the part of the ability attempting to do something to the object will fail to + // do anything. The ability could be unable to find the object because the object never entered the + // specified zone, because it left the zone before the ability resolved, or because it is in a zone that + // is hidden from a player, such as a library or an opponent’s hand. (This rule applies even if the + // object leaves the zone and returns again before the ability resolves.) The most common zone-change + // triggers are enters-the-battlefield triggers and leaves-the-battlefield triggers. + + // There are possible two different use cases: + // * look in current game state (normal events): + // * look back in time (leaves battlefield, dies, etc); + + // TODO: need sync or shared code with AbilityImpl.isInUseableZone + MageObject affectedSourceObject = sourceObject; + if (event == null) { + // state base triggers - use only actual state + } else { + // event triggers - can look back in time for some use cases + switch (event.getType()) { + case ZONE_CHANGE: + ZoneChangeEvent zce = (ZoneChangeEvent) event; + Set eventTargets = CardUtil.getEventTargets(event); + if (eventTargets.contains(getSourceId()) && !zce.getToZone().isPublicZone()) { + // TODO: need research and share with AbilityImpl + // If an ability triggers when the object that has it is put into a hidden zone from a graveyard, + // that ability triggers from the graveyard, (such as Golgari Brownscale), + // Yixlid Jailer will prevent that ability from triggering. + if (zce.getFromZone().match(Zone.GRAVEYARD)) { + if (!CardUtil.cardHadAbility(this, game.getLastKnownInformationCard(getSourceId(), zce.getFromZone()), getSourceId(), game)) { + return false; + } } } - } - if (isLeavesTheBattlefieldTrigger()) { - source = zce.getTarget(); - } - break; - case DESTROYED_PERMANENT: - if (isLeavesTheBattlefieldTrigger()) { - source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); - } - break; + if (isLeavesTheBattlefieldTrigger() && game.checkShortLivingLKI(affectedSourceId, Zone.BATTLEFIELD)) { + affectedSourceObject = game.getLastKnownInformation(affectedSourceId, Zone.BATTLEFIELD); + } + break; + case DESTROYED_PERMANENT: + case EXPLOITED_CREATURE: + if (isLeavesTheBattlefieldTrigger() && game.checkShortLivingLKI(affectedSourceId, Zone.BATTLEFIELD)) { + affectedSourceObject = game.getPermanentOrLKIBattlefield(affectedSourceId); + } + break; + } } - return super.isInUseableZone(game, source, event); + + return super.isInUseableZone(game, affectedSourceObject, event); } - /* - 603.6c Leaves-the-battlefield abilities, 603.6d - if true the game “looks back in time” to determine if those abilities trigger, - using the existence of those abilities and the appearance of objects immediately prior to the event (603.10) - */ @Override public boolean isLeavesTheBattlefieldTrigger() { return leavesTheBattlefieldTrigger; } - /* - 603.6c,603.6d - This has to be set, if the triggered ability has to check back in time if the permanent the ability is connected to had the ability on the battlefield while the trigger is checked - */ @Override public final void setLeavesTheBattlefieldTrigger(boolean leavesTheBattlefieldTrigger) { this.leavesTheBattlefieldTrigger = leavesTheBattlefieldTrigger; + + // TODO: replace override of isInUseableZone in dies only triggers by like "isDiesOnlyTrigger" here } @Override @@ -453,16 +443,35 @@ public TriggeredAbilityImpl setAbilityWord(AbilityWord abilityWord) { } /** + * Looking object in GRAVEYARD zone only. If you need multi zone then use default isInUseableZone + * - good example: Whenever another creature you control dies + * - bad example: When {this} dies or is put into exile from the battlefield + *

* For triggered abilities that function from the battlefield that must trigger when the source permanent dies * and/or for any other events that happen simultaneously to the source permanent dying. * (Similar logic must be used for any leaves-the-battlefield, but this method assumes to graveyard only.) * NOTE: If your ability functions from another zone (not battlefield) then must use standard logic, not this. */ - public static boolean isInUseableZoneDiesTrigger(TriggeredAbility source, GameEvent event, Game game) { - // Get the source permanent of the ability - MageObject sourceObject = null; - if (game.getState().getZone(source.getSourceId()) == Zone.BATTLEFIELD) { - sourceObject = game.getPermanent(source.getSourceId()); + public static boolean isInUseableZoneDiesTrigger(TriggeredAbility sourceAbility, MageObject sourceObject, GameEvent event, Game game) { + // runtime check: wrong trigger settings + if (!sourceAbility.isLeavesTheBattlefieldTrigger()) { + throw new IllegalArgumentException("Wrong code usage: all dies triggers must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + + sourceAbility.getSourceObject(game) + " - " + sourceAbility); + } + + // runtime check: wrong isInUseableZone for batch related triggers + if (event instanceof BatchEvent) { + throw new IllegalArgumentException("Wrong code usage: batch events unsupported here, possible miss of override isInUseableZone - " + + sourceAbility.getSourceObject(game) + " - " + sourceAbility); + } + + // workaround for singleton abilities like Flying + UUID affectedSourceId = getRealSourceObjectId(sourceAbility, sourceObject); + + // on permanent - can use actual or look back in time + MageObject affectedObject = null; + if (game.getState().getZone(affectedSourceId) == Zone.BATTLEFIELD) { + affectedObject = game.getPermanent(affectedSourceId); } else { // The idea: short living LKI must help to find a moment in the inner of resolve // - @@ -480,26 +489,29 @@ public static boolean isInUseableZoneDiesTrigger(TriggeredAbility source, GameEv // - ! empty stack ! graveyard ! no ! no ! no more to resolve // --!---------------!-------------!-----!-----------! // - - if (game.checkShortLivingLKI(source.getSourceId(), Zone.BATTLEFIELD)) { - sourceObject = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); + if (game.checkShortLivingLKI(affectedSourceId, Zone.BATTLEFIELD)) { + affectedObject = game.getLastKnownInformation(affectedSourceId, Zone.BATTLEFIELD); } } - if (sourceObject == null) { // source is no permanent - sourceObject = game.getObject(source); - if (sourceObject == null || sourceObject.isPermanent(game)) { - return false; // No source object found => ability is not valid + + if (affectedObject == null) { + affectedObject = game.getObject(sourceAbility); + if (affectedObject == null || affectedObject.isPermanent(game)) { + // if it was a permanent, but now removed then ignore + return false; } } - if (!source.hasSourceObjectAbility(game, sourceObject, event)) { + if (!sourceAbility.hasSourceObjectAbility(game, affectedObject, event)) { return false; // the permanent does currently not have or before it dies the ability so no trigger } // check now it is in graveyard (only if it is no token and was the target itself) - if (source.getSourceId().equals(event.getTargetId()) // source is also the target - && !(sourceObject instanceof PermanentToken) // it's no token - && sourceObject.getZoneChangeCounter(game) + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) { // It's in the next zone - Zone after = game.getState().getZone(source.getSourceId()); + // TODO: need research + if (affectedSourceId.equals(event.getTargetId()) // source is also the target + && !(affectedObject instanceof PermanentToken) // it's no token + && affectedObject.getZoneChangeCounter(game) + 1 == game.getState().getZoneChangeCounter(affectedSourceId)) { // It's in the next zone + Zone after = game.getState().getZone(affectedSourceId); if (!Zone.GRAVEYARD.match(after)) { // Zone is not the graveyard return false; // Moving to graveyard was replaced so no trigger } diff --git a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java index 6ee178ca922a..17ddc9a8bd57 100644 --- a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java +++ b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java @@ -116,7 +116,7 @@ class CastFromGraveyardOnceWatcher extends Watcher { public void watch(GameEvent event, Game game) { if (GameEvent.EventType.SPELL_CAST.equals(event.getType()) && event.hasApprovingIdentifier(MageIdentifier.CastFromGraveyardOnceWatcher)) { - usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference()); + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); } } diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java index e2903456db2f..6afca9b9c418 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.common; +import mage.MageObject; import mage.MageObjectReference; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -35,6 +36,7 @@ public DealtDamageAndDiedTriggeredAbility(Effect effect, boolean optional, Filte this.filter = filter; this.setTargetPointer = setTargetPointer; setTriggerPhrase(getWhen() + CardUtil.addArticle(filter.getMessage()) + " dealt damage by {this} this turn dies, "); + setLeavesTheBattlefieldTrigger(true); } protected DealtDamageAndDiedTriggeredAbility(final DealtDamageAndDiedTriggeredAbility ability) { @@ -55,6 +57,9 @@ public boolean checkEventType(GameEvent event, Game game) { @Override public boolean checkTrigger(GameEvent event, Game game) { + // If Axelrod Gunnarson and a creature it dealt damage to are both put into a graveyard at the same time, + // Axelrod Gunnarson’s second ability will trigger. + // (2009-10-01) if (((ZoneChangeEvent) event).isDiesEvent()) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; if (filter.match(zEvent.getTarget(), game)) { @@ -78,4 +83,9 @@ public boolean checkTrigger(GameEvent event, Game game) { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java index 1ad19fa02465..04a36d163383 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.common; +import mage.MageObject; import mage.MageObjectReference; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; @@ -32,6 +33,7 @@ public DealtDamageAttachedAndDiedTriggeredAbility(Effect effect, boolean optiona setTriggerPhrase(getWhen() + CardUtil.addArticle(filter.getMessage()) + " dealt damage by " + CardUtil.getTextWithFirstCharLowerCase(attachmentType.verb()) + " creature this turn dies, "); + setLeavesTheBattlefieldTrigger(true); } protected DealtDamageAttachedAndDiedTriggeredAbility(final DealtDamageAttachedAndDiedTriggeredAbility ability) { @@ -75,4 +77,9 @@ public boolean checkTrigger(GameEvent event, Game game) { } return true; } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java index d88602941b5e..b291486ec399 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.common; +import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.cards.Card; @@ -48,6 +49,7 @@ public DiesAttachedTriggeredAbility(Effect effect, String attachedDescription, b this.setTargetPointer = setTargetPointer; this.rememberSource = rememberSource; setTriggerPhrase(generateTriggerPhrase()); + setLeavesTheBattlefieldTrigger(true); } protected DiesAttachedTriggeredAbility(final DiesAttachedTriggeredAbility ability) { @@ -157,4 +159,9 @@ private String generateTriggerPhrase() { } return sb.toString(); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java index cc6f9fc86248..9b885b1bf6e0 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java @@ -48,6 +48,7 @@ public DiesCreatureTriggeredAbility(Zone zone, Effect effect, boolean optional, super(zone, effect, optional); this.filter = filter; this.setTargetPointer = setTargetPointer; + setLeavesTheBattlefieldTrigger(true); setTriggerPhrase("Whenever " + filter.getMessage() + (filter.getMessage().startsWith("one or more") ? " die, " : " dies, ")); } @@ -81,11 +82,11 @@ public boolean checkTrigger(GameEvent event, Game game) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { if (this.zone == Zone.BATTLEFIELD) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } else { - return super.isInUseableZone(game, source, event); + return super.isInUseableZone(game, sourceObject, event); } } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java index 655a7d57839a..a537a79f3596 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java @@ -23,6 +23,7 @@ public DiesOneOrMoreTriggeredAbility(Effect effect, FilterCreaturePermanent filt super(Zone.BATTLEFIELD, effect, optional); this.filter = filter; this.setTriggerPhrase("Whenever one or more " + filter.getMessage() + " die, "); + setLeavesTheBattlefieldTrigger(true); } private DiesOneOrMoreTriggeredAbility(final DiesOneOrMoreTriggeredAbility ability) { @@ -55,10 +56,10 @@ public boolean checkTrigger(GameEvent event, Game game) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { return ((ZoneChangeBatchEvent) event) .getEvents() .stream() - .allMatch(e -> TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, e, game)); + .anyMatch(e -> TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, e, game)); } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesSourceTriggeredAbility.java index 790e94ae41ed..fdadfae27434 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesSourceTriggeredAbility.java @@ -42,7 +42,7 @@ public boolean checkTrigger(GameEvent event, Game game) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java index 8a19656b3236..e55160ee7200 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java @@ -30,6 +30,7 @@ public DiesThisOrAnotherTriggeredAbility(Effect effect, boolean optional, Filter filterMessage = filterMessage.substring(2); } setTriggerPhrase("Whenever {this} or another " + filterMessage + " dies, "); + setLeavesTheBattlefieldTrigger(true); } protected DiesThisOrAnotherTriggeredAbility(final DiesThisOrAnotherTriggeredAbility ability) { @@ -65,7 +66,7 @@ public boolean checkTrigger(GameEvent event, Game game) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java index 9989905a53f5..1a380412bc9c 100644 --- a/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java @@ -48,22 +48,6 @@ public boolean checkEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.EXPLOITED_CREATURE; } - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent sourcePermanent = null; - if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) { - sourcePermanent = game.getPermanent(getSourceId()); - } else { - if (game.checkShortLivingLKI(getSourceId(), Zone.BATTLEFIELD)) { - sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); - } - } - if (sourcePermanent == null) { - return false; - } - return hasSourceObjectAbility(game, sourcePermanent, event); - } - @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getSourceId().equals(getSourceId())) { diff --git a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java index f8cc58d95b0d..26a89a939040 100644 --- a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java @@ -21,6 +21,7 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl { public GodEternalDiesTriggeredAbility() { super(Zone.ALL, null, true); + setLeavesTheBattlefieldTrigger(true); } private GodEternalDiesTriggeredAbility(GodEternalDiesTriggeredAbility ability) { @@ -49,22 +50,6 @@ public boolean checkTrigger(GameEvent event, Game game) { return false; } - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - Permanent sourcePermanent = null; - if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) { - sourcePermanent = game.getPermanent(getSourceId()); - } else { - if (game.checkShortLivingLKI(getSourceId(), Zone.BATTLEFIELD)) { - sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); - } - } - if (sourcePermanent == null) { - return false; - } - return hasSourceObjectAbility(game, sourcePermanent, event); - } - @Override public GodEternalDiesTriggeredAbility copy() { return new GodEternalDiesTriggeredAbility(this); diff --git a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceTriggeredAbility.java index a50025271ad1..cbf4ccd4da2b 100644 --- a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromAnywhereSourceTriggeredAbility.java @@ -41,7 +41,7 @@ protected PutIntoGraveFromAnywhereSourceTriggeredAbility(final PutIntoGraveFromA // * @return // */ // @Override -// public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { +// public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { // if (game.getState().getZone(source.getId()).equals(Zone.GRAVEYARD)) { // return this.hasSourceObjectAbility(game, source, event); // } diff --git a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java index b86baa07a3a5..f67a6bdba61a 100644 --- a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java @@ -26,7 +26,7 @@ public PutIntoGraveFromBattlefieldAllTriggeredAbility(Effect effect, boolean opt public PutIntoGraveFromBattlefieldAllTriggeredAbility(Effect effect, boolean optional, FilterPermanent filter, boolean setTargetPointer, boolean onlyToControllerGraveyard) { super(Zone.BATTLEFIELD, effect, optional); - this.setLeavesTheBattlefieldTrigger(true); + setLeavesTheBattlefieldTrigger(true); this.filter = filter; this.onlyToControllerGraveyard = onlyToControllerGraveyard; this.setTargetPointer = setTargetPointer; @@ -66,7 +66,7 @@ public PutIntoGraveFromBattlefieldAllTriggeredAbility copy() { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldSourceTriggeredAbility.java index 629f3cd1937e..fbff5dc43d1d 100644 --- a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldSourceTriggeredAbility.java @@ -58,7 +58,7 @@ public boolean checkTrigger(GameEvent event, Game game) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } } diff --git a/Mage/src/main/java/mage/abilities/common/ZoneChangeTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ZoneChangeTriggeredAbility.java index 848ef2bc3663..05629813f62a 100644 --- a/Mage/src/main/java/mage/abilities/common/ZoneChangeTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ZoneChangeTriggeredAbility.java @@ -21,7 +21,9 @@ public class ZoneChangeTriggeredAbility extends TriggeredAbilityImpl { protected final Zone toZone; public ZoneChangeTriggeredAbility(Zone fromZone, Zone toZone, Effect effect, String triggerPhrase, boolean optional) { + // fix 3 this(toZone == null ? Zone.ALL : toZone, fromZone, toZone, effect, triggerPhrase, optional); + //this(fromZone == null ? Zone.ALL : fromZone, fromZone, toZone, effect, triggerPhrase, optional); } public ZoneChangeTriggeredAbility(Zone worksInZone, Zone fromZone, Zone toZone, Effect effect, String triggerPhrase, boolean optional) { diff --git a/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java index 136431ae8e05..33f9a066f403 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java @@ -1,9 +1,11 @@ package mage.abilities.common.delayed; +import mage.MageObject; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.Modes; import mage.abilities.TriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.constants.Duration; @@ -27,7 +29,7 @@ public class UntilYourNextTurnDelayedTriggeredAbility extends DelayedTriggeredAb public UntilYourNextTurnDelayedTriggeredAbility(TriggeredAbility ability) { super(null, Duration.UntilYourNextTurn, false); if (ability.isLeavesTheBattlefieldTrigger()) { - this.setLeavesTheBattlefieldTrigger(true); + setLeavesTheBattlefieldTrigger(true); } this.ability = ability; } @@ -103,4 +105,14 @@ public void setSourceObjectZoneChangeCounter(int sourceObjectZoneChangeCounter) public int getSourceObjectZoneChangeCounter() { return ability.getSourceObjectZoneChangeCounter(); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + return super.isInUseableZone(game, sourceObject, event); + } + } } diff --git a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java index 2244f6f8123f..1fd9bdd5f90d 100644 --- a/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/mana/ManaCostImpl.java @@ -259,8 +259,8 @@ public boolean pay(Ability ability, Game game, Ability source, UUID controllerId return false; } - // TODO: is it require Phyrexian stile effects here for single payment? - //AbilityImpl.preparePhyrexianCost(game, source, player, ability, this); + // no needs to call + //AbilityImpl.handlePhyrexianLikeEffects(game, source, ability, this); if (!player.getManaPool().isForcedToPay()) { assignPayment(game, ability, player.getManaPool(), costToPay != null ? costToPay : this); diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java index b8e0b6f57d30..ab4e86f5d4d5 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.decorator; +import mage.MageObject; import mage.abilities.Modes; import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -7,6 +8,7 @@ import mage.abilities.effects.Effect; import mage.abilities.effects.Effects; import mage.constants.EffectType; +import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.util.CardUtil; @@ -38,7 +40,7 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm public ConditionalInterveningIfTriggeredAbility(TriggeredAbility ability, Condition condition, String text) { super(ability.getZone(), null); if (ability.isLeavesTheBattlefieldTrigger()) { - this.setLeavesTheBattlefieldTrigger(true); + setLeavesTheBattlefieldTrigger(true); } this.ability = ability; this.condition = condition; @@ -133,4 +135,14 @@ public int getSourceObjectZoneChangeCounter() { public boolean caresAboutManaColor() { return condition.caresAboutManaColor(); } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + return super.isInUseableZone(game, sourceObject, event); + } + } } diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java index cb12f7b2b66d..4a2b89ce8f51 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.decorator; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.Modes; import mage.abilities.TriggeredAbility; @@ -41,6 +42,9 @@ public ConditionalTriggeredAbility(TriggeredAbility ability, Condition condition this.ability = ability; this.condition = condition; this.abilityText = text; + if (ability.isLeavesTheBattlefieldTrigger()) { + this.setLeavesTheBattlefieldTrigger(true); + } } protected ConditionalTriggeredAbility(final ConditionalTriggeredAbility triggered) { @@ -118,4 +122,13 @@ public Ability withFlavorWord(String flavorWord) { return this; } + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + return super.isInUseableZone(game, sourceObject, event); + } + } } diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java index 8dd4e6cf9eaf..5ad74ac0fb1b 100644 --- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java @@ -325,7 +325,7 @@ public boolean checkIfThereArePayCostToAttackBlockEffects(GameEvent event, Game if (effect instanceof PayCostToAttackBlockEffect) { Set abilities = replacementEffects.getAbility(effect.getId()); for (Ability ability : abilities) { - // for replacment effects of static abilities do not use LKI to check if to apply + // for replacement effects of static abilities do not use LKI to check if to apply if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, event)) { if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) { if (!game.getScopeRelevant() || effect.hasSelfScope() || !event.getTargetId().equals(ability.getSourceId())) { @@ -370,7 +370,7 @@ private Map> getApplicableReplacementEffects(Gam Set abilities = replacementEffects.getAbility(effect.getId()); Set applicableAbilities = new HashSet<>(); for (Ability ability : abilities) { - // for replacment effects of static abilities do not use LKI to check if to apply + // for replacement effects of static abilities do not use LKI to check if to apply if (ability.getAbilityType() != AbilityType.STATIC || ability.isInUseableZone(game, null, event)) { if (!effect.isUsed()) { if (!game.getScopeRelevant() diff --git a/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java b/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java index c6343ba97e8a..f4d0a650c5b6 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java @@ -47,6 +47,7 @@ public HauntAbility(Card card, Effect effect) { setTriggerPhrase((creatureHaunt ? "When {this} enters or the creature it haunts dies, " : "When the creature {this} haunts dies, ") ); + setLeavesTheBattlefieldTrigger(true); } private HauntAbility(final HauntAbility ability) { @@ -128,7 +129,7 @@ private HauntExileAbility(final HauntExileAbility ability) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { boolean fromOK = true; Permanent sourcePermanent = (Permanent) game.getLastKnownInformation(sourceId, Zone.BATTLEFIELD); if (creatureHaunt diff --git a/Mage/src/main/java/mage/abilities/keyword/RecoverAbility.java b/Mage/src/main/java/mage/abilities/keyword/RecoverAbility.java index 3bcd02f36dbe..6b07d43f59ec 100644 --- a/Mage/src/main/java/mage/abilities/keyword/RecoverAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/RecoverAbility.java @@ -1,20 +1,16 @@ - package mage.abilities.keyword; -import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.ExileSourceEffect; import mage.abilities.effects.common.ReturnToHandSourceEffect; import mage.cards.Card; -import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; -import mage.players.Player; /** * 702.58a Recover is a triggered ability that functions only while the card @@ -28,7 +24,8 @@ public class RecoverAbility extends TriggeredAbilityImpl { public RecoverAbility(Cost cost, Card card) { - super(Zone.GRAVEYARD, new RecoverEffect(cost, card.isCreature()), false); + super(Zone.GRAVEYARD, new RecoverEffect(cost, card), false); + setLeavesTheBattlefieldTrigger(true); } protected RecoverAbility(final RecoverAbility ability) { @@ -64,19 +61,15 @@ public String getRule() { } } -class RecoverEffect extends OneShotEffect { - - protected Cost cost; +class RecoverEffect extends DoIfCostPaid { - public RecoverEffect(Cost cost, boolean creature) { - super(Outcome.ReturnToHand); - this.cost = cost; - this.staticText = setText(cost, creature); + public RecoverEffect(Cost cost, Card card) { + super(new ReturnToHandSourceEffect(), new ExileSourceEffect(), cost); + this.staticText = setText(cost, card.isCreature()); } protected RecoverEffect(final RecoverEffect effect) { super(effect); - this.cost = effect.cost.copy(); } @Override @@ -84,23 +77,6 @@ public RecoverEffect copy() { return new RecoverEffect(this); } - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - Card sourceCard = game.getCard(source.getSourceId()); - if (controller != null && sourceCard != null - && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { - if (controller.chooseUse(Outcome.Damage, "Pay " + cost.getText() + " to recover " + sourceCard.getLogName() + "? (Otherwise the card will be exiled)", source, game)) { - cost.clearPaid(); - if (cost.pay(source, game, source, controller.getId(), false, null)) { - return new ReturnToHandSourceEffect().apply(game, source); - } - } - return new ExileSourceEffect().apply(game, source); - } - return false; - } - private String setText(Cost cost, boolean creature) { StringBuilder sb = new StringBuilder(); sb.append("Recover"); diff --git a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java index c5fce43af6cc..2342564f6621 100644 --- a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java @@ -1,5 +1,6 @@ package mage.abilities.meta; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbilityImpl; @@ -10,10 +11,7 @@ import mage.util.CardUtil; import mage.watchers.Watcher; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; /** @@ -46,6 +44,10 @@ public OrTriggeredAbility(Zone zone, Effect effect, boolean optional, String rul for(Watcher watcher : ability.getWatchers()) { super.addWatcher(watcher); } + + if (ability.isLeavesTheBattlefieldTrigger()) { + setLeavesTheBattlefieldTrigger(true); + } } setTriggerPhrase(generateTriggerPhrase()); } @@ -123,4 +125,19 @@ public void addWatcher(Watcher watcher) { ability.addWatcher(watcher); } } + + @Override + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + boolean res = false; + for (TriggeredAbility ability : triggeredAbilities) { + // TODO: call full inner trigger instead like ability.isInUseableZone()?! Need research why it fails + if (ability.isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + res |= TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); + } else { + res |= super.isInUseableZone(game, sourceObject, event); + } + } + return res; + } } diff --git a/Mage/src/main/java/mage/designations/Monarch.java b/Mage/src/main/java/mage/designations/Monarch.java index 01666c624609..20cbc62b599a 100644 --- a/Mage/src/main/java/mage/designations/Monarch.java +++ b/Mage/src/main/java/mage/designations/Monarch.java @@ -61,7 +61,7 @@ public MonarchDrawTriggeredAbility copy() { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { return true; } @@ -108,7 +108,7 @@ public MonarchDealsCombatDamageToAPlayerTriggeredAbility copy() { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { return true; } diff --git a/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java b/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java index f56faea246da..0a2dabc7efb3 100644 --- a/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java +++ b/Mage/src/main/java/mage/game/command/emblems/DackFaydenEmblem.java @@ -63,7 +63,7 @@ public boolean checkEventType(GameEvent event, Game game) { @Override public boolean checkTrigger(GameEvent event, Game game) { boolean returnValue = false; - List targetedPermanentIds = new ArrayList<>(0); + List targetedPermanentIds = new ArrayList<>(); Player player = game.getPlayer(this.getControllerId()); if (player != null) { if (event.getPlayerId().equals(this.getControllerId())) { diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 90a34cf5ee37..73d985e94936 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -511,11 +511,6 @@ combines all permanent damage events to a single batch (event) and split it per DAMAGED_BATCH_FOR_ONE_PERMANENT(true), DESTROY_PERMANENT, - /* DESTROY_PERMANENT_BY_LEGENDARY_RULE - targetId id of the permanent to destroy - playerId controller of the permanent to detroy - */ - DESTROY_PERMANENT_BY_LEGENDARY_RULE, /* DESTROYED_PERMANENT targetId id of the destroyed creature sourceId sourceId of the ability with the destroy effect @@ -819,16 +814,12 @@ public void setZone(Zone zone) { } /** - * Returns possibly approving object that allowed the creation of the event. + * Returns possibly approving object that allowed the creation of the event. Used for cast spell and play land events. */ - public ApprovingObject getAdditionalReference() { + public ApprovingObject getApprovingObject() { return approvingObject; } - public void setAdditionalReference(ApprovingObject approvingObject) { - this.approvingObject = approvingObject; - } - /** * used to store which replacement effects were already applied to an event * or any modified events that may replace it diff --git a/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java b/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java index 0ed685d81509..576723e93c17 100644 --- a/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java +++ b/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java @@ -96,8 +96,6 @@ public Zone getOriginalToZone() { /** * Source ability of the event, can be null in rare cases - * - * @return */ public Ability getSource() { return this.source; @@ -105,6 +103,9 @@ public Ability getSource() { @Override public String toString() { - return super.toString() + ", from " + getFromZone() + " to " + getToZone(); + return super.toString() + + ", from " + getFromZone() + " to " + getToZone() + + ", " + (this.target == null ? "no target" : "target " + this.target) + + ", " + (this.source == null ? "no source" : "source " + this.source); } } diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index ccb394e13a47..46f0a89a656b 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -1255,11 +1255,26 @@ public boolean entersBattlefield(Ability source, Game game, Zone fromZone, boole } // own etb event + // 616.1a + // If any of the replacement and/or prevention effects are self-replacement effects (see rule 614.15), + // one of them must be chosen. If not, proceed to rule 616.1b. if (game.replaceEvent(new EntersTheBattlefieldEvent(this, source, getControllerId(), fromZone, EnterEventType.SELF))) { return false; } + // 616.1b + // If any of the replacement and/or prevention effects would modify under whose control an object would + // enter the battlefield, one of them must be chosen. If not, proceed to rule 616.1c. + // TODO: need implementation? See #13062 + + // 616.1c + // If any of the replacement and/or prevention effects would cause an object to become a copy of another + // object as it enters the battlefield, one of them must be chosen. If not, proceed to rule 616.1d. + // TODO: need implementation? See #13062 + // normal etb event + // 616.1d + // Any of the applicable replacement and/or prevention effects may be chosen. EntersTheBattlefieldEvent event = new EntersTheBattlefieldEvent(this, source, getControllerId(), fromZone); if (game.replaceEvent(event)) { return false; diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index d87f9fdf8713..4f9244234ba3 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -517,12 +517,12 @@ public boolean canChooseTarget(Game game, UUID playerId) { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { throw new UnsupportedOperationException("Not supported."); } @Override - public boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event) { + public boolean hasSourceObjectAbility(Game game, MageObject sourceObject, GameEvent event) { throw new UnsupportedOperationException("Not supported."); } diff --git a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java index c94d9db9f48b..cb79e86e0a5f 100644 --- a/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java +++ b/Mage/src/main/java/mage/target/targetpointer/NthTargetPointer.java @@ -15,7 +15,7 @@ */ public abstract class NthTargetPointer extends TargetPointerImpl { - private static final List emptyTargets = Collections.unmodifiableList(new ArrayList<>(0)); + private static final List emptyTargets = Collections.unmodifiableList(new ArrayList<>()); // TODO: rework to list of MageObjectReference instead zcc private final Map zoneChangeCounter = new HashMap<>(); diff --git a/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java b/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java index 2ac2b578add5..fb29f87ac282 100644 --- a/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/OnceEachTurnCastWatcher.java @@ -32,7 +32,7 @@ public void watch(GameEvent event, Game game) { && event.getPlayerId() != null && event.hasApprovingIdentifier(MageIdentifier.OnceEachTurnCastWatcher)) { usedFrom.computeIfAbsent(event.getPlayerId(), k -> new HashSet<>()) - .add(event.getAdditionalReference().getApprovingMageObjectReference()); + .add(event.getApprovingObject().getApprovingMageObjectReference()); } }