diff --git a/Mage.Sets/src/mage/cards/a/AjanisLastStand.java b/Mage.Sets/src/mage/cards/a/AjanisLastStand.java index d0288164c7e..a2697f89c90 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 @@ class AjanisLastStandTriggeredAbility extends TriggeredAbilityImpl { new CreateTokenEffect(new AvatarToken2()), new SacrificeSourceCost() ), false); + setLeavesTheBattlefieldTrigger(true); } private AjanisLastStandTriggeredAbility(final AjanisLastStandTriggeredAbility ability) { @@ -98,4 +101,9 @@ class AjanisLastStandTriggeredAbility extends TriggeredAbilityImpl { + "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 16948746ae7..33c317738ce 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 @@ class AthreosDiesCreatureTriggeredAbility extends TriggeredAbilityImpl { } 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 f0f2df5cc95..8debc0e7bcb 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 dac578fcc71..1c6baec7753 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 @@ class AvacynsCollarTriggeredAbility extends TriggeredAbilityImpl { 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 9f90fc52865..05fb4ff62c1 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 @@ class BereavementTriggeredAbility extends TriggeredAbilityImpl { 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 a89d8103604..0be4df74c26 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 6c9e7cf4a97..2a514af68ca 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 final class CallerOfTheClaw extends CardImpl { // 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 5d42400bf1d..af484ad7a79 100644 --- a/Mage.Sets/src/mage/cards/c/CarthTheLion.java +++ b/Mage.Sets/src/mage/cards/c/CarthTheLion.java @@ -58,6 +58,7 @@ class CarthTheLionTriggeredAbility extends TriggeredAbilityImpl { 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 5b527f865a0..95111180226 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 9142b8bcffb..1fb0d5e1482 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 014bd5b749e..6eca6368d9e 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 39edd855a93..97f3248396b 100644 --- a/Mage.Sets/src/mage/cards/c/Chronozoa.java +++ b/Mage.Sets/src/mage/cards/c/Chronozoa.java @@ -33,7 +33,7 @@ public final class Chronozoa extends CardImpl { // 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 b8f3a117abf..40b9f884318 100644 --- a/Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java +++ b/Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java @@ -237,15 +237,15 @@ class CoramTheUndertakerWatcher extends Watcher { 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 8cf9f4510b2..adce8898a57 100644 --- a/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java +++ b/Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java @@ -226,7 +226,7 @@ class CourtOfLocthwainWatcher extends Watcher { && 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 8b7b30d757e..774f1cbf73f 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 5c2826e3451..18d0feed6a4 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 @@ class DeathTyrantTriggeredAbility extends TriggeredAbilityImpl { } @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 619f967e0b1..8ccfce29cac 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 @@ class DeathsPresenceTriggeredAbility extends TriggeredAbilityImpl { 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 bc8f05ca387..8e932387792 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 @@ class DiabolicServitudeCreatureDiesTriggeredAbility extends TriggeredAbilityImpl 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 0e35ac6c7bb..9db8a67e7ee 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 @@ class DiregrafCaptainTriggeredAbility extends TriggeredAbilityImpl { 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 be6fe64a348..3df8fc02676 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 e259defedc2..d1cf80be8c0 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 @@ class EndlessEvilBounceAbility extends TriggeredAbilityImpl { 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 797e6e6f567..a1f0488b0cc 100644 --- a/Mage.Sets/src/mage/cards/e/EnigmaSphinx.java +++ b/Mage.Sets/src/mage/cards/e/EnigmaSphinx.java @@ -3,6 +3,7 @@ package mage.cards.e; 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 @@ class EnigmaSphinxTriggeredAbility extends TriggeredAbilityImpl { 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 @@ class EnigmaSphinxTriggeredAbility extends TriggeredAbilityImpl { } 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 d42736a2ec9..5f098d88de5 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 ed0621fd7e1..01cae39b36b 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 @@ class FalkenrathNobleTriggeredAbility extends TriggeredAbilityImpl { 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 @@ class FalkenrathNobleTriggeredAbility extends TriggeredAbilityImpl { 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 8e7d5446332..d1101dfc643 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 2436daa2dab..011cd01ca5a 100644 --- a/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java +++ b/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java @@ -91,7 +91,7 @@ class GaleaKindlerOfHopeTriggeredAbility extends TriggeredAbilityImpl { 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 a8ffb3d46b9..6466b2cbcad 100644 --- a/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java +++ b/Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java @@ -162,7 +162,7 @@ class GlimpseTheCosmosWatcher extends Watcher { 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 af4674f4632..4d70006e4cc 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 @@ class GraveBetrayalTriggeredAbility extends TriggeredAbilityImpl { 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 @@ class GraveBetrayalEffect extends OneShotEffect { } 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 c58990753c0..4c5a036722d 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 @@ class GravePactTriggeredAbility extends TriggeredAbilityImpl { } 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 ca06176f03e..956f177edff 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 @@ class GutterGrimeTriggeredAbility extends TriggeredAbilityImpl { 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 a615cf25fc9..3a0e4298251 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 @@ class HatefulEidolonTriggeredAbility extends TriggeredAbilityImpl { 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 ad5a6cd5dee..19b896ecdd0 100644 --- a/Mage.Sets/src/mage/cards/h/HaukensInsight.java +++ b/Mage.Sets/src/mage/cards/h/HaukensInsight.java @@ -185,7 +185,7 @@ class HaukensInsightWatcher extends Watcher { 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 650fbff8f08..04d061f1302 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 20494b03b74..2ec090a52d5 100644 --- a/Mage.Sets/src/mage/cards/i/IanMalcolmChaotician.java +++ b/Mage.Sets/src/mage/cards/i/IanMalcolmChaotician.java @@ -224,7 +224,7 @@ class IanMalcolmChaoticianWatcher extends Watcher { 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 ce789ae7194..f57fbfb3197 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 6aaf3d51784..a4c24be192a 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 @@ class InfestedThrinaxTriggeredAbility extends DelayedTriggeredAbility { 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 0b514ae90af..606bec7fe1e 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 5830ad878e6..9f4b47eaaa3 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 09dcd7ea130..68bb6406d27 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 e52b32c15e9..cc1ab89fcaf 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 @@ class KayaTheInexorableTriggeredAbility extends TriggeredAbilityImpl { 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 0cd641eb58c..de7433f4804 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 64f349b7699..7deead6e67f 100644 --- a/Mage.Sets/src/mage/cards/k/KessDissidentMage.java +++ b/Mage.Sets/src/mage/cards/k/KessDissidentMage.java @@ -178,9 +178,9 @@ class KessDissidentMageWatcher extends Watcher { && 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 b02bc89141e..4e6aa44c4b6 100644 --- a/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java +++ b/Mage.Sets/src/mage/cards/k/KotoseTheSilentSpider.java @@ -170,7 +170,7 @@ class KotoseTheSilentSpiderWatcher extends Watcher { 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 @@ class KotoseTheSilentSpiderWatcher extends Watcher { 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 fd540af7c71..866c5237622 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 8da7d9ba86d..916f03d6777 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 @@ class LuminousBroodmothTriggeredAbility extends TriggeredAbilityImpl { 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 3366ed15c9b..4bb11c18f64 100644 --- a/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java +++ b/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java @@ -70,6 +70,7 @@ class LyndeCheerfulTormentorCurseDiesTriggeredAbility extends TriggeredAbilityIm new LyndeCheerfulTormentorReturnCurseEffect() ) )); + setLeavesTheBattlefieldTrigger(true); } private LyndeCheerfulTormentorCurseDiesTriggeredAbility(final LyndeCheerfulTormentorCurseDiesTriggeredAbility ability) { @@ -102,8 +103,8 @@ class LyndeCheerfulTormentorCurseDiesTriggeredAbility extends TriggeredAbilityIm } @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 06518125704..a4e8a5a4ab1 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 9f28c451fd0..81fc10fb684 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheBridge.java @@ -2,6 +2,7 @@ package mage.cards.m; 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 @@ class MagusOfTheBridgeTriggeredAbility extends TriggeredAbilityImpl { } 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 78d3dd1e562..f93dee68cf2 100644 --- a/Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java +++ b/Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java @@ -117,11 +117,11 @@ class MarchOfRecklessJoyWatcher 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.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 e4358d79f2b..a2593ee86a9 100644 --- a/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java +++ b/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java @@ -3,6 +3,7 @@ package mage.cards.m; 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 @@ class MarchesaTheBlackRoseTriggeredAbility extends TriggeredAbilityImpl { } 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 70f9e008ffc..be800e48089 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 @@ class MariTheKillingQuillCreatureDiesAbility extends TriggeredAbilityImpl { 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 0eb4d9d7ad0..7183de40072 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 @@ class MartyrsBondTriggeredAbility extends TriggeredAbilityImpl { 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 219837f3e6a..fa5de8db62e 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 @@ class MassacreGirlDelayedTriggeredAbility extends DelayedTriggeredAbility { 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 a90806c0799..ef0fde681c5 100644 --- a/Mage.Sets/src/mage/cards/m/MassacreWurm.java +++ b/Mage.Sets/src/mage/cards/m/MassacreWurm.java @@ -2,6 +2,7 @@ package mage.cards.m; 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 @@ class MassacreWurmTriggeredAbility extends TriggeredAbilityImpl { } 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 25571adba98..a69602dea49 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 @@ class MillicentRestlessRevenantTriggeredAbility extends TriggeredAbilityImpl { } @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 984c13b86c0..8d7a377ec93 100644 --- a/Mage.Sets/src/mage/cards/m/MimicVat.java +++ b/Mage.Sets/src/mage/cards/m/MimicVat.java @@ -4,6 +4,8 @@ package mage.cards.m; 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 @@ class MimicVatTriggeredAbility extends TriggeredAbilityImpl { 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 1346b665656..696f0ad2c06 100644 --- a/Mage.Sets/src/mage/cards/m/MolderBeast.java +++ b/Mage.Sets/src/mage/cards/m/MolderBeast.java @@ -3,6 +3,7 @@ package mage.cards.m; 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 @@ class MolderBeastTriggeredAbility extends TriggeredAbilityImpl { 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 25a974f5b09..f9ec0bc9b19 100644 --- a/Mage.Sets/src/mage/cards/m/MuldrothaTheGravetide.java +++ b/Mage.Sets/src/mage/cards/m/MuldrothaTheGravetide.java @@ -146,8 +146,8 @@ class MuldrothaTheGravetideWatcher extends Watcher { 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 @@ class MuldrothaTheGravetideWatcher extends Watcher { 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 9afb6ae5ce9..51f3d1f815b 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 @@ class MycoidShepherdTriggeredAbility extends TriggeredAbilityImpl { 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 1c78fd0ffde..95eebdf01f9 100644 --- a/Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java +++ b/Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java @@ -121,7 +121,7 @@ class NashiMoonSagesScionWatcher extends Watcher { 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 @@ class NashiMoonSagesScionWatcher extends Watcher { 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 c68159e3503..dc9742b15c4 100644 --- a/Mage.Sets/src/mage/cards/n/Necroskitter.java +++ b/Mage.Sets/src/mage/cards/n/Necroskitter.java @@ -3,6 +3,7 @@ package mage.cards.n; 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 @@ class NecroskitterTriggeredAbility extends TriggeredAbilityImpl { 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 a4d9f339b6c..3b4f04d605d 100644 --- a/Mage.Sets/src/mage/cards/n/NetherTraitor.java +++ b/Mage.Sets/src/mage/cards/n/NetherTraitor.java @@ -3,6 +3,7 @@ package mage.cards.n; 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 @@ class NetherTraitorTriggeredAbility extends TriggeredAbilityImpl { 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 3dcefa47b45..3dca6eb6eeb 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 @@ class NimDeathmantleTriggeredAbility extends TriggeredAbilityImpl { 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 dd2780ae3d3..26719137046 100644 --- a/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java +++ b/Mage.Sets/src/mage/cards/o/OneWithTheMultiverse.java @@ -136,7 +136,7 @@ class OneWithTheMultiverseWatcher extends Watcher { 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 4180c056097..96a82a5c3e8 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 @@ class OrahSkyclaveHierophantTriggeredAbility extends TriggeredAbilityImpl { 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 b4b8c86adb4..7460ec51914 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 @@ class PatronOfTheVeinCreatureDiesTriggeredAbility extends TriggeredAbilityImpl { 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 95b6af1183c..848dd821458 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 9466bd0b5c3..ba73a50278f 100644 --- a/Mage.Sets/src/mage/cards/p/PhantasmalImage.java +++ b/Mage.Sets/src/mage/cards/p/PhantasmalImage.java @@ -44,9 +44,7 @@ public final class PhantasmalImage extends CardImpl { 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 402bc1e6ff5..cf9897d2572 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 @@ class PiasRevolutionTriggeredAbility extends TriggeredAbilityImpl { } 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 4f2eac1f9da..0e46e0e563a 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 @@ class ProperBurialTriggeredAbility extends TriggeredAbilityImpl { 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 e04b189242c..ee633d13cb7 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 05c5dc84327..295053af5f3 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 @@ class PurgatoryTriggeredAbility extends TriggeredAbilityImpl { 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 edbd306c6e1..42d0d8dab13 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 347bf2c4b58..a9fe28c4235 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 @@ class RemembranceTriggeredAbility extends TriggeredAbilityImpl { "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 c957bce8e86..8223b5f7c3c 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 b7a81dcb1a6..3bce95e4211 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 @@ class RhukHexgoldNabberTriggeredAbility extends TriggeredAbilityImpl { 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 d69857b0063..850fb69a03f 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 @@ class RienneAngelOfRebirthTriggeredAbility extends TriggeredAbilityImpl { 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 baea68dc46d..7fa043d7480 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 @@ class SacredGroundTriggeredAbility extends TriggeredAbilityImpl { 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 fb7e27358f2..90f62fec037 100644 --- a/Mage.Sets/src/mage/cards/s/Sangromancer.java +++ b/Mage.Sets/src/mage/cards/s/Sangromancer.java @@ -4,6 +4,7 @@ package mage.cards.s; 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 final class Sangromancer extends CardImpl { } 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 @@ class SangromancerFirstTriggeredAbility extends TriggeredAbilityImpl { } 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 ed51d7e4994..80078d64cbe 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 @@ class ScrapTrawlerTriggeredAbility extends TriggeredAbilityImpl { 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 @@ class ScrapTrawlerTriggeredAbility extends TriggeredAbilityImpl { } 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 a8274337aea..013e4aa5aaf 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 @@ class ScrapheapTriggeredAbility extends TriggeredAbilityImpl { 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 @@ class ScrapheapTriggeredAbility extends TriggeredAbilityImpl { 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 435b71fe139..86ec5fb253f 100644 --- a/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java +++ b/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java @@ -2,6 +2,7 @@ package mage.cards.s; 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 @@ class SeerOfStolenSightTriggeredAbility extends TriggeredAbilityImpl { .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 a9b21caa7b5..cb450af8b94 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 5c188cb07b4..908d79747fb 100644 --- a/Mage.Sets/src/mage/cards/s/SerraParagon.java +++ b/Mage.Sets/src/mage/cards/s/SerraParagon.java @@ -193,7 +193,7 @@ class SerraParagonWatcher extends Watcher { || 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 e82071a39ad..9ffd88a72c9 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 4d32904e1e6..51cd8a005ce 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 @@ class ShelobChildOfUngoliantTriggeredAbility extends TriggeredAbilityImpl { 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 @@ class ShelobChildOfUngoliantTriggeredAbility extends TriggeredAbilityImpl { } 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 635d5590fc4..53cee240701 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 @@ class SlagstoneRefineryTriggeredAbility extends TriggeredAbilityImpl { 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 0eba2e6116d..56e9742fb9e 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 @@ class SlayersPlateTriggeredAbility extends TriggeredAbilityImpl { 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 2ff18d9e74d..61960d3cf7c 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 @@ class SporogenesisTriggeredAbility extends TriggeredAbilityImpl { 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 91eeb02bc3c..fad8498bc0b 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 @@ class SyrKonradTheGrimTriggeredAbility extends TriggeredAbilityImpl { } @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 9637aaff982..7412aeb4948 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 632c9a2b0cb..8a03884f9e0 100644 --- a/Mage.Sets/src/mage/cards/t/TheScorpionGod.java +++ b/Mage.Sets/src/mage/cards/t/TheScorpionGod.java @@ -3,6 +3,7 @@ package mage.cards.t; 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 @@ class TheScorpionGodTriggeredAbility extends TriggeredAbilityImpl { 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 530092dd1e5..ff220b1df5a 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 @@ class TheSkullsporeNexusTrigger extends TriggeredAbilityImpl { } @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 a3bdcbb7a44..658f2bfb8c2 100644 --- a/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java +++ b/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java @@ -3,6 +3,7 @@ package mage.cards.t; 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 @@ class TianaShipsCaretakerTriggeredAbility extends TriggeredAbilityImpl { } 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 5e984e60aa5..8181f4d3f12 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 b435f3b8a62..3e0b602b9e2 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 @@ class VerdantSuccessionTriggeredAbility extends TriggeredAbilityImpl { 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 fc7bd04d0cf..40f766221b7 100644 --- a/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java +++ b/Mage.Sets/src/mage/cards/v/VesuvanShapeshifter.java @@ -114,7 +114,7 @@ class VesuvanShapeshifterEffect extends OneShotEffect { 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 f3d9aaec997..9fb2dab04a7 100644 --- a/Mage.Sets/src/mage/cards/v/VillageCannibals.java +++ b/Mage.Sets/src/mage/cards/v/VillageCannibals.java @@ -3,6 +3,7 @@ package mage.cards.v; 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 @@ class VillageCannibalsTriggeredAbility extends TriggeredAbilityImpl { 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 89c82ab21cf..34af2943e23 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 @@ class VindictiveVampireTriggeredAbility extends TriggeredAbilityImpl { } @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 79212a3654d..9606f89b43e 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 final class ViridianRevel extends CardImpl { } class ViridianRevelTriggeredAbility extends TriggeredAbilityImpl { + ViridianRevelTriggeredAbility() { super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), true); + setLeavesTheBattlefieldTrigger(true); } private ViridianRevelTriggeredAbility(final ViridianRevelTriggeredAbility ability) { @@ -75,4 +79,9 @@ class ViridianRevelTriggeredAbility extends TriggeredAbilityImpl { 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 be011c45707..74c8976c803 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 2c467c3c5f4..dc430487ce8 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 e892d64e088..e94568dec23 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 @@ class XiraTheGoldenStingTriggeredAbility extends WhenTargetDiesDelayedTriggeredA 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 540539aa1c9..da8b6749bd8 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 class ConniveTest extends CardTestPlayerBase { 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 class ConniveTest extends CardTestPlayerBase { 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 class ConniveTest extends CardTestPlayerBase { // 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 class ConniveTest extends CardTestPlayerBase { 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 941fcf4ab14..a9a210b6156 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 class ExploitTest extends CardTestPlayerBase { 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 8bccbf9446b..f2e12c3e5e0 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 class IncubateTest extends CardTestPlayerBase { @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 class IncubateTest extends CardTestPlayerBase { @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 class IncubateTest extends CardTestPlayerBase { // 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 class IncubateTest extends CardTestPlayerBase { // 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 9cd0ae1aa3a..5984367ae39 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.junit.Test; 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 class RecoverTest extends CardTestPlayerBase { 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 class RecoverTest extends CardTestPlayerBase { } @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 class RecoverTest extends CardTestPlayerBase { 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 class RecoverTest extends CardTestPlayerBase { 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 3b066bf2145..75ef87401b0 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 class PhantasmalImageTest extends CardTestPlayerBase { 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 fbc6887c764..cd1c006c3d1 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 class ZoneChangeReplacementTest extends CardTestPlayerBase { 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 555d9793b02..fe28717b08c 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 class GeodeGolemTest extends CardTestCommanderDuelBase { 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 1df34463bf4..50823474d0f 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 class VerrakWarpedSengirTest extends CardTestPlayerBase { 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 00000000000..8fa520e87af --- /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 9558b74aecc..4c3b4dca6bb 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 class GluttonousHellkiteTest extends CardTestPlayerBase { @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 4949f349a9c..19ebc89e380 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 class ChronozoaTest extends CardTestPlayerBase { 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 dd6088f56ff..eec48b69efa 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 class SacrificeDiesTriggerTest extends CardTestPlayerBase { @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 class SacrificeDiesTriggerTest extends CardTestPlayerBase { @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 00000000000..8a0fee2735e --- /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 0cb9e99549e..9f63da3acc7 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 @@ import org.mage.test.serverside.base.CardTestPlayerBase; /** * Tests the {@link mage.abilities.common.DiesOneOrMoreTriggeredAbility} batching. * - * @author Susucr + * @author Susucr, JayDi85 */ public class VengefulTownsfolkTest extends CardTestPlayerBase { @@ -145,4 +145,52 @@ public class VengefulTownsfolkTest extends CardTestPlayerBase { 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 490f33bb0a2..418673e9d12 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 @@ import org.mage.test.serverside.base.CardTestCommanderDuelBase; * * @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 ca7e6c859e8..4b9deb86cf9 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 @@ public abstract class MageTestPlayerBase { /** * 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 @@ public abstract class MageTestPlayerBase { /** * 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 @@ public abstract class MageTestPlayerBase { /** * 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, @@ -369,11 +368,21 @@ public abstract class MageTestPlayerBase { } /** - * Add target transform ability that can be called by text "target transform" - * - * @param controller + * Add all destroy ability that can be called by text "all destroy" */ - protected void addCustomEffect_TransformTarget(TestPlayer controller) { + 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" + */ + 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 @@ public abstract class MageTestPlayerBase { /** * 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 @@ public abstract class MageTestPlayerBase { /** * 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 064801a98cf..e018ec86f55 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.ObjectColor; 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 @@ public class VerifyCardDataTest { 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 @@ public class VerifyCardDataTest { } 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 @@ public class VerifyCardDataTest { } 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 ab458969a70..96f1a194195 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 @@ public interface Ability extends Controllable, Serializable { 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 @@ public interface Ability extends Controllable, Serializable { /** * 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 0830467cbdb..214b7b015a9 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.Game; 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 @@ public abstract class AbilityImpl implements Ability { 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 - return true; - } - MageObject object = game.getObject(this.getSourceId()); - // emblem/planes are always actual - if (object instanceof Emblem || object instanceof Dungeon || object instanceof Plane) { + 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; + } } } - UUID parameterSourceId; - // 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 - if (this instanceof MageSingleton && source != null) { - parameterSourceId = source.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())) { + // 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; } - // check against current state - Zone test = game.getState().getZone(parameterSourceId); - return zone.match(test); + + // 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; + } + + 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 (sourceAbility instanceof MageSingleton && sourceObject != null) { + return sourceObject.getId(); + } else { + return sourceAbility.getSourceId(); + } } @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 f826eff405f..3e00c3ca6de 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 abstract class StaticAbility extends AbilityImpl { } } - @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 9d8ab33cef6..3670dc461f6 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilities.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilities.java @@ -209,7 +209,7 @@ public class TriggeredAbilities extends LinkedHashMap 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 7d690c6a862..f47a10f76c6 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 29030e2bf3f..a119c31c1bb 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.AbilityType; 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 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge // 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 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, 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. - */ + // workaround for singleton abilities like Flying + UUID affectedSourceId = getRealSourceObjectId(this, sourceObject); - Set eventTargets = CardUtil.getEventTargets(event); - if (!eventTargets.contains(getSourceId())) { - return super.isInUseableZone(game, source, 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. - 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; + // 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 abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } /** + * 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 abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge // - ! 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 (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 (game.checkShortLivingLKI(affectedSourceId, Zone.BATTLEFIELD)) { + affectedObject = game.getLastKnownInformation(affectedSourceId, Zone.BATTLEFIELD); } } - if (!source.hasSourceObjectAbility(game, sourceObject, event)) { + 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 (!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 6ee178ca922..17ddc9a8bd5 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 e2903456db2..6afca9b9c41 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 class DealtDamageAndDiedTriggeredAbility extends TriggeredAbilityImpl { 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 class DealtDamageAndDiedTriggeredAbility extends TriggeredAbilityImpl { @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 class DealtDamageAndDiedTriggeredAbility extends TriggeredAbilityImpl { } 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 1ad19fa0246..04a36d16338 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 class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility 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 class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility } 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 d88602941b5..b291486ec39 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 class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl { this.setTargetPointer = setTargetPointer; this.rememberSource = rememberSource; setTriggerPhrase(generateTriggerPhrase()); + setLeavesTheBattlefieldTrigger(true); } protected DiesAttachedTriggeredAbility(final DiesAttachedTriggeredAbility ability) { @@ -157,4 +159,9 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl { } 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 cc6f9fc8624..9b885b1bf6e 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 class DiesCreatureTriggeredAbility extends TriggeredAbilityImpl { 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 class DiesCreatureTriggeredAbility extends TriggeredAbilityImpl { } @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 655a7d57839..a537a79f359 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 class DiesOneOrMoreTriggeredAbility extends TriggeredAbilityImpl implemen 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 class DiesOneOrMoreTriggeredAbility extends TriggeredAbilityImpl implemen } @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 790e94ae41e..fdadfae2743 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 class DiesSourceTriggeredAbility extends ZoneChangeTriggeredAbility { } @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 8a19656b323..e55160ee720 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 class DiesThisOrAnotherTriggeredAbility extends TriggeredAbilityImpl { filterMessage = filterMessage.substring(2); } setTriggerPhrase("Whenever {this} or another " + filterMessage + " dies, "); + setLeavesTheBattlefieldTrigger(true); } protected DiesThisOrAnotherTriggeredAbility(final DiesThisOrAnotherTriggeredAbility ability) { @@ -65,7 +66,7 @@ public class DiesThisOrAnotherTriggeredAbility extends TriggeredAbilityImpl { } @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 9989905a53f..1a380412bc9 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 class ExploitCreatureTriggeredAbility extends TriggeredAbilityImpl { 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 f8cc58d95b0..26a89a93904 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 class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl { 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 a50025271ad..cbf4ccd4da2 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 @@ public class PutIntoGraveFromAnywhereSourceTriggeredAbility extends ZoneChangeTr // * @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 b86baa07a3a..f67a6bdba61 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 class PutIntoGraveFromBattlefieldAllTriggeredAbility extends TriggeredAbi 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 class PutIntoGraveFromBattlefieldAllTriggeredAbility extends TriggeredAbi } @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 629f3cd1937..fbff5dc43d1 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 class PutIntoGraveFromBattlefieldSourceTriggeredAbility extends Triggered } @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 848ef2bc366..05629813f62 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 136431ae8e0..33f9a066f40 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 class UntilYourNextTurnDelayedTriggeredAbility extends DelayedTriggeredAb 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 2244f6f8123..1fd9bdd5f90 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 abstract class ManaCostImpl extends CostImpl implements ManaCost { 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 b8e0b6f57d3..ab4e86f5d4d 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.condition.Condition; 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 class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm 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 cb12f7b2b66..4a2b89ce8f5 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 class ConditionalTriggeredAbility extends TriggeredAbilityImpl { 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 class ConditionalTriggeredAbility extends TriggeredAbilityImpl { 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 8dd4e6cf9ea..5ad74ac0fb1 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 class ContinuousEffects implements Serializable { 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 @@ public class ContinuousEffects implements Serializable { 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 c6343ba97e8..f4d0a650c5b 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 class HauntAbility extends TriggeredAbilityImpl { 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 @@ class HauntExileAbility extends ZoneChangeTriggeredAbility { } @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 3bcd02f36db..6b07d43f59e 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 @@ import mage.players.Player; 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 class RecoverAbility extends TriggeredAbilityImpl { } } -class RecoverEffect extends OneShotEffect { +class RecoverEffect extends DoIfCostPaid { - protected Cost cost; - - 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 @@ class RecoverEffect extends OneShotEffect { 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 c5fce43af6c..2342564f662 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.game.events.GameEvent; 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 class OrTriggeredAbility extends TriggeredAbilityImpl { for(Watcher watcher : ability.getWatchers()) { super.addWatcher(watcher); } + + if (ability.isLeavesTheBattlefieldTrigger()) { + setLeavesTheBattlefieldTrigger(true); + } } setTriggerPhrase(generateTriggerPhrase()); } @@ -123,4 +125,19 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl { 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 01666c62460..20cbc62b599 100644 --- a/Mage/src/main/java/mage/designations/Monarch.java +++ b/Mage/src/main/java/mage/designations/Monarch.java @@ -61,7 +61,7 @@ class MonarchDrawTriggeredAbility extends BeginningOfEndStepTriggeredAbility { } @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 @@ class MonarchDealsCombatDamageToAPlayerTriggeredAbility extends TriggeredAbility } @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 f56faea246d..0a2dabc7efb 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 @@ class DackFaydenEmblemTriggeredAbility extends TriggeredAbilityImpl { @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 90a34cf5ee3..73d985e9493 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 @@ public class GameEvent implements Serializable { 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 class GameEvent implements Serializable { } /** - * 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 0ed685d8150..576723e93c1 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 class ZoneChangeEvent extends GameEvent { /** * Source ability of the event, can be null in rare cases - * - * @return */ public Ability getSource() { return this.source; @@ -105,6 +103,9 @@ public class ZoneChangeEvent extends GameEvent { @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 ccb394e13a4..46f0a89a656 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 abstract class PermanentImpl extends CardImpl implements Permanent { } // 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 d87f9fdf871..4f9244234ba 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 class StackAbility extends StackObjectImpl implements Ability { } @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 c94d9db9f48..cb79e86e0a5 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 @@ import java.util.*; */ 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 2ac2b578add..fb29f87ac28 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 class OnceEachTurnCastWatcher extends Watcher { && event.getPlayerId() != null && event.hasApprovingIdentifier(MageIdentifier.OnceEachTurnCastWatcher)) { usedFrom.computeIfAbsent(event.getPlayerId(), k -> new HashSet<>()) - .add(event.getAdditionalReference().getApprovingMageObjectReference()); + .add(event.getApprovingObject().getApprovingMageObjectReference()); } }