From 66b338c6fccb760b3f4e7c6b23aa098b4549dcba Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 4 Nov 2024 23:55:14 +0400 Subject: [PATCH 01/12] dies triggers improves: * tests: added additional tests and verify/runtime checks for wrong die trigger settings; * refactor: removed some usage of short LKI ; * fixed dies events support in "or trigger" and "conditional trigger" (use cases like sacrifice cost); * fixed dies events support in shared triggered abilities (use cases like sacrifice cost); --- .../test/cards/copy/PhantasmalImageTest.java | 62 +++++++++++++++ .../single/dmc/VerrakWarpedSengirTest.java | 21 +++++ .../cards/triggers/dies/ChronozoaTest.java | 2 +- .../java/mage/verify/VerifyCardDataTest.java | 14 ++++ .../src/main/java/mage/abilities/Ability.java | 7 +- .../main/java/mage/abilities/AbilityImpl.java | 76 ++++++++++++++++--- .../java/mage/abilities/StaticAbility.java | 2 +- .../java/mage/abilities/TriggeredAbility.java | 9 +++ .../mage/abilities/TriggeredAbilityImpl.java | 23 +++--- .../common/DiesCreatureTriggeredAbility.java | 1 + .../DiesThisOrAnotherTriggeredAbility.java | 1 + .../ExploitCreatureTriggeredAbility.java | 16 ---- .../GodEternalDiesTriggeredAbility.java | 17 +---- .../abilities/costs/mana/ManaCostImpl.java | 4 +- ...ditionalInterveningIfTriggeredAbility.java | 12 +++ .../abilities/meta/OrTriggeredAbility.java | 25 +++++- .../command/emblems/DackFaydenEmblem.java | 2 +- .../targetpointer/NthTargetPointer.java | 2 +- 18 files changed, 233 insertions(+), 63 deletions(-) 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/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/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.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 064801a98cf..270b22da238 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,23 @@ 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 + card.getAbilities().stream() + .filter(a -> a instanceof TriggeredAbility) + .map(a -> (TriggeredAbility) a) + .filter(a -> a.getRule().contains("whenever") || a.getRule().contains("Whenever")) + .filter(a -> a.getRule().contains("dies")) + .filter(a -> !a.getRule().contains("with \"When")) // ignore token creating effects + .filter(a -> !a.isLeavesTheBattlefieldTrigger()) + .forEach(a -> { + fail(card, "abilities", "dies trigger must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + a.getClass().getSimpleName()); + }); + // special check: duplicated words in ability text (wrong target/filter usage) // example: You may exile __two two__ blue cards // possible fixes: diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index ab458969a70..a3300a0092f 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -351,7 +351,12 @@ 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) */ boolean isInUseableZone(Game game, MageObject source, GameEvent event); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 0830467cbdb..6552a59b112 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,11 +1174,6 @@ 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)) { @@ -1201,13 +1198,74 @@ public abstract class AbilityImpl implements Ability { } else { parameterSourceId = getSourceId(); } + + // old code: + // TODO: delete after dies fix // check against shortLKI for effects that move multiple object at the same time (e.g. destroy all) if (game.checkShortLivingLKI(getSourceId(), getZone())) { - return true; + //return true; // fix 1 } - // 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 lookingInZone = game.getState().getZone(parameterSourceId); + + // 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 (source instanceof Permanent && isTriggerCanFireAfterLeaveBattlefield(event)) { + // support leaves-the-battlefield abilities + lookingInZone = 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(lookingInZone); + } + + 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: add more events with zone change logic (or make it even't param)? + switch (e.getType()) { + case DESTROYED_PERMANENT: + case EXPLOITED_CREATURE: + return true; + case ZONE_CHANGE: + return ((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD; + default: + return false; + } + }); } @Override diff --git a/Mage/src/main/java/mage/abilities/StaticAbility.java b/Mage/src/main/java/mage/abilities/StaticAbility.java index f826eff405f..37ef2a1de64 100644 --- a/Mage/src/main/java/mage/abilities/StaticAbility.java +++ b/Mage/src/main/java/mage/abilities/StaticAbility.java @@ -27,7 +27,7 @@ public abstract class StaticAbility extends AbilityImpl { @Override public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - if (game.checkShortLivingLKI(getSourceId(), zone)) { + if (game.checkShortLivingLKI(getSourceId(), zone)) { // TODO: can be deleted? Need research return true; // maybe this can be a problem if effects removed the ability from the object } if (game.getPermanentEntering(getSourceId()) != null && zone == Zone.BATTLEFIELD) { diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbility.java b/Mage/src/main/java/mage/abilities/TriggeredAbility.java index 7d690c6a862..61c60ae9b31 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbility.java @@ -62,8 +62,17 @@ 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 + */ 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..1fd3d0059a4 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -49,7 +49,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()); - } } @@ -400,6 +399,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } break; case DESTROYED_PERMANENT: + case EXPLOITED_CREATURE: if (isLeavesTheBattlefieldTrigger()) { source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); } @@ -408,20 +408,11 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge return super.isInUseableZone(game, source, 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; @@ -453,12 +444,22 @@ 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) { + // runtime check: wrong trigger settings + if (!source.isLeavesTheBattlefieldTrigger()) { + // TODO: enable after fix + // throw new IllegalArgumentException("Wrong code usage: all dies triggers must use setLeavesTheBattlefieldTrigger(true)"); + } + // Get the source permanent of the ability MageObject sourceObject = null; if (game.getState().getZone(source.getSourceId()) == Zone.BATTLEFIELD) { @@ -481,7 +482,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge // --!---------------!-------------!-----!-----------! // - if (game.checkShortLivingLKI(source.getSourceId(), Zone.BATTLEFIELD)) { - sourceObject = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); + sourceObject = game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); } } if (sourceObject == null) { // source is no permanent diff --git a/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java index cc6f9fc8624..421d95bd9be 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, ")); } diff --git a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java index 8a19656b323..96851179e44 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) { 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..d280ed8f614 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); + this.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/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..dfb28160590 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; @@ -133,4 +135,14 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm public boolean caresAboutManaColor() { return condition.caresAboutManaColor(); } + + @Override + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + if (isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } else { + return super.isInUseableZone(game, source, event); + } + } } diff --git a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java index c5fce43af6c..90bc73e0e0e 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()) { + this.setLeavesTheBattlefieldTrigger(true); + } } setTriggerPhrase(generateTriggerPhrase()); } @@ -123,4 +125,19 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl { ability.addWatcher(watcher); } } + + @Override + public boolean isInUseableZone(Game game, MageObject source, 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, event, game); + } else { + res |= super.isInUseableZone(game, source, event); + } + } + return res; + } } 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/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<>(); From 0f8416cfb199205861b09ca0dc7cdb1811bc73f4 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 4 Nov 2024 23:56:25 +0400 Subject: [PATCH 02/12] * fixed dies events support in single cards (use cases like sacrifice cost); --- Mage.Sets/src/mage/cards/c/Chronozoa.java | 2 +- Mage.Sets/src/mage/cards/f/FalkenrathNoble.java | 8 ++++++++ Mage.Sets/src/mage/cards/g/GraveBetrayal.java | 9 ++++++++- Mage.Sets/src/mage/cards/g/GravePact.java | 8 +++++++- Mage.Sets/src/mage/cards/k/KarmicJustice.java | 1 + .../src/mage/cards/k/KayaTheInexorable.java | 17 +---------------- .../src/mage/cards/m/MarchesaTheBlackRose.java | 7 +++++++ Mage.Sets/src/mage/cards/m/MassacreWurm.java | 7 +++++++ Mage.Sets/src/mage/cards/m/MimicVat.java | 8 ++++++++ Mage.Sets/src/mage/cards/m/MycoidShepherd.java | 6 ++++++ Mage.Sets/src/mage/cards/n/Necroskitter.java | 7 +++++++ .../mage/cards/o/OrahSkyclaveHierophant.java | 7 +++++++ Mage.Sets/src/mage/cards/p/PatronOfTheVein.java | 6 ++++++ Mage.Sets/src/mage/cards/p/PhantasmalImage.java | 4 +--- .../src/mage/cards/r/ReyhanLastOfTheAbzan.java | 1 + .../mage/cards/s/ShelobChildOfUngoliant.java | 6 ++++++ .../src/mage/cards/v/VindictiveVampire.java | 12 ++---------- 17 files changed, 84 insertions(+), 32 deletions(-) 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/f/FalkenrathNoble.java b/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java index ed0621fd7e1..7d2845925d8 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()); + this.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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/g/GraveBetrayal.java b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java index af4674f4632..4b9f1f4195e 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); + this.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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, 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..2b945d92cd7 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, "); + setLeavesTheBattlefieldTrigger(true); } private GravePactTriggeredAbility(final GravePactTriggeredAbility ability) { @@ -74,6 +75,11 @@ class GravePactTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class GravePactEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/k/KarmicJustice.java b/Mage.Sets/src/mage/cards/k/KarmicJustice.java index 09dcd7ea130..cfe8dc6401f 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); + 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..5d9de44e970 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); + 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/m/MarchesaTheBlackRose.java b/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java index e4358d79f2b..8ec3e3ea3cc 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class MarchesaTheBlackRoseEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MassacreWurm.java b/Mage.Sets/src/mage/cards/m/MassacreWurm.java index a90806c0799..fed541ad199 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/m/MimicVat.java b/Mage.Sets/src/mage/cards/m/MimicVat.java index 984c13b86c0..4c42f835460 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class MimicVatEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MycoidShepherd.java b/Mage.Sets/src/mage/cards/m/MycoidShepherd.java index 9afb6ae5ce9..4a1ff8a742c 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/Necroskitter.java b/Mage.Sets/src/mage/cards/n/Necroskitter.java index c68159e3503..a1f620abf7e 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java b/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java index 4180c056097..26d3c229d5c 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java index b4b8c86adb4..0d94c5d9249 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class PatronOfTheVeinExileCreatureEffect extends OneShotEffect { 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/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/s/ShelobChildOfUngoliant.java b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java index 4d32904e1e6..49caa883d55 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; @@ -185,6 +186,11 @@ class ShelobChildOfUngoliantTriggeredAbility extends TriggeredAbilityImpl { } return false; } + + @Override + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class ShelobChildOfUngoliantEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/v/VindictiveVampire.java b/Mage.Sets/src/mage/cards/v/VindictiveVampire.java index 89c82ab21cf..1a6f257b23a 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) { @@ -70,16 +71,7 @@ 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); + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); } @Override From c3343110f3f4052bd8e31048a1da8dc90e14a19b Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 6 Nov 2024 21:29:27 +0400 Subject: [PATCH 03/12] refactor: fixed dies events support in single cards (part 2); --- Mage.Sets/src/mage/cards/f/FalkenrathNoble.java | 2 +- Mage.Sets/src/mage/cards/g/GraveBetrayal.java | 2 +- Mage.Sets/src/mage/cards/g/GravePact.java | 2 +- Mage.Sets/src/mage/cards/k/KarmicJustice.java | 2 +- Mage.Sets/src/mage/cards/k/KayaTheInexorable.java | 2 +- Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java | 7 +++++++ Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java | 7 +++++++ .../src/mage/cards/m/MillicentRestlessRevenant.java | 7 ++++++- Mage.Sets/src/mage/cards/p/PeltCollector.java | 1 + Mage.Sets/src/mage/cards/p/ProperBurial.java | 7 +++++++ Mage.Sets/src/mage/cards/r/Remembrance.java | 7 +++++++ Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java | 11 +++++++++++ Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java | 7 +++++++ Mage.Sets/src/mage/cards/s/Sangromancer.java | 8 ++++++++ .../src/mage/cards/s/ShelobChildOfUngoliant.java | 1 + Mage.Sets/src/mage/cards/s/SlayersPlate.java | 8 ++++++++ Mage.Sets/src/mage/cards/s/Sporogenesis.java | 7 +++++++ Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java | 1 + Mage.Sets/src/mage/cards/t/TheScorpionGod.java | 7 +++++++ Mage.Sets/src/mage/cards/v/VerdantSuccession.java | 6 ++++++ Mage.Sets/src/mage/cards/v/VillageCannibals.java | 7 +++++++ .../src/test/java/mage/verify/VerifyCardDataTest.java | 2 ++ .../java/mage/abilities/TriggeredAbilityImpl.java | 2 ++ .../common/GodEternalDiesTriggeredAbility.java | 2 +- ...utIntoGraveFromBattlefieldAllTriggeredAbility.java | 2 +- .../abilities/common/ZoneChangeTriggeredAbility.java | 2 ++ .../UntilYourNextTurnDelayedTriggeredAbility.java | 2 +- .../ConditionalInterveningIfTriggeredAbility.java | 2 +- .../java/mage/abilities/meta/OrTriggeredAbility.java | 2 +- 29 files changed, 114 insertions(+), 11 deletions(-) diff --git a/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java b/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java index 7d2845925d8..ca82f007fd5 100644 --- a/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java +++ b/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java @@ -53,7 +53,7 @@ class FalkenrathNobleTriggeredAbility extends TriggeredAbilityImpl { super(Zone.BATTLEFIELD, new LoseLifeTargetEffect(1), false); this.addEffect(new GainLifeEffect(1)); this.addTarget(new TargetPlayer()); - this.setLeavesTheBattlefieldTrigger(true); + setLeavesTheBattlefieldTrigger(true); } private FalkenrathNobleTriggeredAbility(final FalkenrathNobleTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/g/GraveBetrayal.java b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java index 4b9f1f4195e..74d02b03726 100644 --- a/Mage.Sets/src/mage/cards/g/GraveBetrayal.java +++ b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java @@ -55,7 +55,7 @@ class GraveBetrayalTriggeredAbility extends TriggeredAbilityImpl { public GraveBetrayalTriggeredAbility() { super(Zone.BATTLEFIELD, null); - this.setLeavesTheBattlefieldTrigger(true); + setLeavesTheBattlefieldTrigger(true); } private GraveBetrayalTriggeredAbility(final GraveBetrayalTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/g/GravePact.java b/Mage.Sets/src/mage/cards/g/GravePact.java index 2b945d92cd7..1fd5127bd28 100644 --- a/Mage.Sets/src/mage/cards/g/GravePact.java +++ b/Mage.Sets/src/mage/cards/g/GravePact.java @@ -49,7 +49,7 @@ class GravePactTriggeredAbility extends TriggeredAbilityImpl { public GravePactTriggeredAbility() { super(Zone.BATTLEFIELD, new GravePactEffect()); setTriggerPhrase("Whenever a creature you control dies, "); - setLeavesTheBattlefieldTrigger(true); + this.setLeavesTheBattlefieldTrigger(true); } private GravePactTriggeredAbility(final GravePactTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/k/KarmicJustice.java b/Mage.Sets/src/mage/cards/k/KarmicJustice.java index cfe8dc6401f..68bb6406d27 100644 --- a/Mage.Sets/src/mage/cards/k/KarmicJustice.java +++ b/Mage.Sets/src/mage/cards/k/KarmicJustice.java @@ -44,7 +44,7 @@ class KarmicJusticeTriggeredAbility extends TriggeredAbilityImpl { KarmicJusticeTriggeredAbility() { super(Zone.BATTLEFIELD, new DestroyTargetEffect(), true); - setLeavesTheBattlefieldTrigger(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 5d9de44e970..cc1ab89fcaf 100644 --- a/Mage.Sets/src/mage/cards/k/KayaTheInexorable.java +++ b/Mage.Sets/src/mage/cards/k/KayaTheInexorable.java @@ -78,7 +78,7 @@ class KayaTheInexorableTriggeredAbility extends TriggeredAbilityImpl { public KayaTheInexorableTriggeredAbility() { super(Zone.ALL, null, false); - setLeavesTheBattlefieldTrigger(true); + this.setLeavesTheBattlefieldTrigger(true); } private KayaTheInexorableTriggeredAbility(KayaTheInexorableTriggeredAbility ability) { diff --git a/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java b/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java index 8da7d9ba86d..9b28106c602 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class LuminousBroodmothEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java b/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java index 70f9e008ffc..a7212e18e92 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class MariTheKillingQuillExileCreatureEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java b/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java index 25571adba98..7f2dba51de3 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) { @@ -125,7 +126,11 @@ class MillicentRestlessRevenantTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } else { + return super.isInUseableZone(game, source, event); + } } @Override 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/ProperBurial.java b/Mage.Sets/src/mage/cards/p/ProperBurial.java index 4f2eac1f9da..fab4cee4729 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/r/Remembrance.java b/Mage.Sets/src/mage/cards/r/Remembrance.java index 347bf2c4b58..b510e445d34 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java b/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java index b7a81dcb1a6..49c8106d498 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 source, GameEvent event) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } else { + return super.isInUseableZone(game, source, 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..f0aca8d28d7 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class RienneAngelOfRebirthEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/s/Sangromancer.java b/Mage.Sets/src/mage/cards/s/Sangromancer.java index fb7e27358f2..84cd3e50487 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class SangromancerSecondTriggeredAbility extends TriggeredAbilityImpl { diff --git a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java index 49caa883d55..8bf7976f1fa 100644 --- a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java +++ b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java @@ -146,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) { diff --git a/Mage.Sets/src/mage/cards/s/SlayersPlate.java b/Mage.Sets/src/mage/cards/s/SlayersPlate.java index 0eba2e6116d..c102ddfd9e8 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/Sporogenesis.java b/Mage.Sets/src/mage/cards/s/Sporogenesis.java index 2ff18d9e74d..ddbf72aa1b9 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, 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..050f9e5c5b6 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) { diff --git a/Mage.Sets/src/mage/cards/t/TheScorpionGod.java b/Mage.Sets/src/mage/cards/t/TheScorpionGod.java index 632c9a2b0cb..6e50ba4cbdf 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class TheScorpionGodEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/v/VerdantSuccession.java b/Mage.Sets/src/mage/cards/v/VerdantSuccession.java index b435f3b8a62..61f8de91be2 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class VerdantSuccessionEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/v/VillageCannibals.java b/Mage.Sets/src/mage/cards/v/VillageCannibals.java index f3d9aaec997..be700813d5b 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 270b22da238..7480120691b 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -1992,6 +1992,8 @@ public class VerifyCardDataTest { .filter(a -> a.getRule().contains("whenever") || a.getRule().contains("Whenever")) .filter(a -> a.getRule().contains("dies")) .filter(a -> !a.getRule().contains("with \"When")) // ignore token creating effects + .filter(a -> !a.getRule().contains("gains \"When")) // ignore token creating effects + .filter(a -> !a.getRule().contains("and \"When")) // ignore token creating effects .filter(a -> !a.isLeavesTheBattlefieldTrigger()) .forEach(a -> { fail(card, "abilities", "dies trigger must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + a.getClass().getSimpleName()); diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index 1fd3d0059a4..d4cbc67565d 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -416,6 +416,8 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge @Override public final void setLeavesTheBattlefieldTrigger(boolean leavesTheBattlefieldTrigger) { this.leavesTheBattlefieldTrigger = leavesTheBattlefieldTrigger; + + // TODO: replace override of isInUseableZone in dies only triggers by like "isDiesOnlyTrigger" here } @Override diff --git a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java index d280ed8f614..26a89a93904 100644 --- a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java @@ -21,7 +21,7 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl { public GodEternalDiesTriggeredAbility() { super(Zone.ALL, null, true); - this.setLeavesTheBattlefieldTrigger(true); + setLeavesTheBattlefieldTrigger(true); } private GodEternalDiesTriggeredAbility(GodEternalDiesTriggeredAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java index b86baa07a3a..f6e12c78806 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; 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..98c7a9dba5b 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java @@ -27,7 +27,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; } diff --git a/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java index dfb28160590..68a75a9274f 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java @@ -40,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; diff --git a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java index 90bc73e0e0e..b79d1b84210 100644 --- a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java @@ -46,7 +46,7 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl { } if (ability.isLeavesTheBattlefieldTrigger()) { - this.setLeavesTheBattlefieldTrigger(true); + setLeavesTheBattlefieldTrigger(true); } } setTriggerPhrase(generateTriggerPhrase()); From dc9f3498287c56ace4142c19768be6bc3bedece1 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 9 Nov 2024 17:55:07 +0400 Subject: [PATCH 04/12] refactor: fixed dies events support in single cards (part 3); --- .../src/mage/cards/d/DaxosBlessedByTheSun.java | 1 + Mage.Sets/src/mage/cards/d/DeathTyrant.java | 1 + Mage.Sets/src/mage/cards/d/Dreadhound.java | 1 + Mage.Sets/src/mage/cards/g/GutterGrime.java | 6 ++++++ Mage.Sets/src/mage/cards/h/HatefulEidolon.java | 7 +++++++ Mage.Sets/src/mage/cards/i/InfestedThrinax.java | 8 ++++++++ .../src/mage/cards/j/JerrenCorruptedBishop.java | 1 + Mage.Sets/src/mage/cards/m/MassacreGirl.java | 8 ++++++++ .../test/java/mage/verify/VerifyCardDataTest.java | 10 ++++++++++ Mage/src/main/java/mage/abilities/AbilityImpl.java | 4 ++-- .../DealtDamageAttachedAndDiedTriggeredAbility.java | 7 +++++++ .../common/DiesAttachedTriggeredAbility.java | 7 +++++++ .../UntilYourNextTurnDelayedTriggeredAbility.java | 12 ++++++++++++ .../decorator/ConditionalTriggeredAbility.java | 13 +++++++++++++ 14 files changed, 84 insertions(+), 2 deletions(-) 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..0a670188b4c 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) { 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/g/GutterGrime.java b/Mage.Sets/src/mage/cards/g/GutterGrime.java index ca06176f03e..a5044904968 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, 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..4f92ff17ce7 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/i/InfestedThrinax.java b/Mage.Sets/src/mage/cards/i/InfestedThrinax.java index 6aaf3d51784..a8c84cd0b61 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, 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/m/MassacreGirl.java b/Mage.Sets/src/mage/cards/m/MassacreGirl.java index 219837f3e6a..10be05d22dd 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } \ No newline at end of file diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 7480120691b..7ea9473f5bb 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -1994,6 +1994,9 @@ public class VerifyCardDataTest { .filter(a -> !a.getRule().contains("with \"When")) // ignore token creating effects .filter(a -> !a.getRule().contains("gains \"When")) // ignore token creating effects .filter(a -> !a.getRule().contains("and \"When")) // ignore token creating effects + .filter(a -> !card.getName().equals("Massacre Girl") // delayed trigger fixed, but verify check can't find it + && !card.getName().equals("Infested Thrinax") + ) .filter(a -> !a.isLeavesTheBattlefieldTrigger()) .forEach(a -> { fail(card, "abilities", "dies trigger must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + a.getClass().getSimpleName()); @@ -2319,6 +2322,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; @@ -2326,6 +2332,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/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 6552a59b112..de6448aff7b 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1255,13 +1255,13 @@ public abstract class AbilityImpl implements Ability { } return allEvents.stream().anyMatch(e -> { - // TODO: add more events with zone change logic (or make it even't param)? + // TODO: add more events with zone change logic (or make it event's param)? switch (e.getType()) { case DESTROYED_PERMANENT: case EXPLOITED_CREATURE: return true; case ZONE_CHANGE: - return ((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD; + return ((ZoneChangeEvent) e).getFromZone() == Zone.BATTLEFIELD; default: return false; } diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java index 1ad19fa0246..e0c3e7de9de 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, 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..2befcc668bc 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } 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 98c7a9dba5b..74950868014 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; @@ -103,4 +105,14 @@ public class UntilYourNextTurnDelayedTriggeredAbility extends DelayedTriggeredAb public int getSourceObjectZoneChangeCounter() { return ability.getSourceObjectZoneChangeCounter(); } + + @Override + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + if (isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } else { + return super.isInUseableZone(game, source, 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..c6aad85b6bf 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 source, GameEvent event) { + if (isLeavesTheBattlefieldTrigger()) { + // TODO: leaves battlefield and die are not same! Is it possible make a diff logic? + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } else { + return super.isInUseableZone(game, source, event); + } + } } From 0689c56597298a511bfdcf6957b16cdb353d2816 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 9 Nov 2024 19:10:00 +0400 Subject: [PATCH 05/12] refactor: fixed dies events support in single cards (part 4); --- .../src/mage/cards/a/AjanisLastStand.java | 8 +++ .../src/mage/cards/a/AthreosGodOfPassage.java | 7 +++ .../src/mage/cards/a/AthreosShroudVeiled.java | 1 + Mage.Sets/src/mage/cards/a/AvacynsCollar.java | 7 +++ Mage.Sets/src/mage/cards/b/Bereavement.java | 8 +++ Mage.Sets/src/mage/cards/c/CarthTheLion.java | 1 + .../src/mage/cards/d/DeathsPresence.java | 11 +++- .../src/mage/cards/d/DiregrafCaptain.java | 9 ++- .../src/mage/cards/x/XiraTheGoldenSting.java | 8 +++ .../cards/abilities/keywords/ConniveTest.java | 8 +-- .../abilities/keywords/IncubateTest.java | 12 ++-- .../test/cards/single/c18/GeodeGolemTest.java | 2 +- .../cards/single/m19/AjanisLastStandTest.java | 57 +++++++++++++++++++ .../single/mh3/GluttonousHellkiteTest.java | 2 +- .../dies/SacrificeDiesTriggerTest.java | 4 +- .../serverside/base/MageTestPlayerBase.java | 34 ++++++----- .../java/mage/verify/VerifyCardDataTest.java | 4 +- .../main/java/mage/abilities/AbilityImpl.java | 3 + .../DealtDamageAndDiedTriggeredAbility.java | 10 ++++ 19 files changed, 159 insertions(+), 37 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/m19/AjanisLastStandTest.java diff --git a/Mage.Sets/src/mage/cards/a/AjanisLastStand.java b/Mage.Sets/src/mage/cards/a/AjanisLastStand.java index d0288164c7e..f8c85f68c8b 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java b/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java index 16948746ae7..4f489a953a8 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, 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..bdbbf64e78c 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/b/Bereavement.java b/Mage.Sets/src/mage/cards/b/Bereavement.java index 9f90fc52865..cb2389b6502 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } 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/d/DeathsPresence.java b/Mage.Sets/src/mage/cards/d/DeathsPresence.java index 619f967e0b1..c4b7e395008 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java b/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java index 0e35ac6c7bb..b68be216556 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java b/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java index e892d64e088..f23f6b45cbb 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, 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/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/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/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/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/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index ca7e6c859e8..c5e1d336f16 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,10 +349,8 @@ 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) { Ability ability = new SimpleActivatedAbility(new DestroyTargetEffect().setText("target destroy"), new ManaCostsImpl<>("")); ability.addTarget(new TargetPermanent()); addCustomCardWithAbility( @@ -369,11 +361,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 +387,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 +403,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 7ea9473f5bb..19782eb047e 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -1989,15 +1989,17 @@ public class VerifyCardDataTest { card.getAbilities().stream() .filter(a -> a instanceof TriggeredAbility) .map(a -> (TriggeredAbility) a) + .filter(a -> !a.isLeavesTheBattlefieldTrigger()) .filter(a -> a.getRule().contains("whenever") || a.getRule().contains("Whenever")) .filter(a -> a.getRule().contains("dies")) .filter(a -> !a.getRule().contains("with \"When")) // ignore token creating effects .filter(a -> !a.getRule().contains("gains \"When")) // ignore token creating effects .filter(a -> !a.getRule().contains("and \"When")) // ignore token creating effects + .filter(a -> !a.getRule().contains("dies while {this} is in your graveyard")) // ignore Boneyard Scourge .filter(a -> !card.getName().equals("Massacre Girl") // delayed trigger fixed, but verify check can't find it && !card.getName().equals("Infested Thrinax") + && !card.getName().equals("Xira, the Golden Sting") ) - .filter(a -> !a.isLeavesTheBattlefieldTrigger()) .forEach(a -> { fail(card, "abilities", "dies trigger must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + a.getClass().getSimpleName()); }); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index de6448aff7b..ecde1db6777 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1256,6 +1256,9 @@ public abstract class AbilityImpl implements Ability { return allEvents.stream().anyMatch(e -> { // 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 switch (e.getType()) { case DESTROYED_PERMANENT: case EXPLOITED_CREATURE: diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java index e2903456db2..66efee58cfe 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } From fdbc5d6409171189c858f0581a8f9733abadf7b4 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 10 Nov 2024 15:51:52 +0400 Subject: [PATCH 06/12] docs: added additional notes for 616, related to #13062 --- .../java/mage/game/permanent/PermanentImpl.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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; From 52ebba4cd125960f1cfb805a7d3fb7496a83a663 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 12 Nov 2024 00:18:08 +0400 Subject: [PATCH 07/12] refactor: removed outdated code --- .../src/mage/cards/c/ChainerNightmareAdept.java | 4 ++-- Mage.Sets/src/mage/cards/c/ChandraHopesBeacon.java | 4 ++-- .../src/mage/cards/c/ChissGoriaForgeTyrant.java | 4 ++-- Mage.Sets/src/mage/cards/c/CoramTheUndertaker.java | 8 ++++---- Mage.Sets/src/mage/cards/c/CourtOfLocthwain.java | 2 +- Mage.Sets/src/mage/cards/e/EvelynTheCovetous.java | 4 ++-- Mage.Sets/src/mage/cards/f/Flameskull.java | 5 ++--- Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java | 2 +- Mage.Sets/src/mage/cards/g/GlimpseTheCosmos.java | 2 +- Mage.Sets/src/mage/cards/h/HaukensInsight.java | 2 +- Mage.Sets/src/mage/cards/h/HedonistsTrove.java | 4 ++-- .../src/mage/cards/i/IanMalcolmChaotician.java | 2 +- Mage.Sets/src/mage/cards/i/IdolOfEndurance.java | 4 ++-- .../src/mage/cards/k/KaghaShadowArchdruid.java | 2 +- Mage.Sets/src/mage/cards/k/KessDissidentMage.java | 4 ++-- .../src/mage/cards/k/KotoseTheSilentSpider.java | 4 ++-- .../src/mage/cards/l/LobeliaDefenderOfBagEnd.java | 4 ++-- Mage.Sets/src/mage/cards/m/MaestrosAscendancy.java | 6 +++--- Mage.Sets/src/mage/cards/m/MarchOfRecklessJoy.java | 4 ++-- .../src/mage/cards/m/MuldrothaTheGravetide.java | 8 ++++---- Mage.Sets/src/mage/cards/n/NashiMoonSagesScion.java | 4 ++-- .../src/mage/cards/o/OneWithTheMultiverse.java | 2 +- .../src/mage/cards/r/RadiantScrollwielder.java | 4 ++-- Mage.Sets/src/mage/cards/s/SerpentsSoulJar.java | 4 ++-- Mage.Sets/src/mage/cards/s/SerraParagon.java | 2 +- Mage.Sets/src/mage/cards/u/UnluckyWitness.java | 4 ++-- Mage.Sets/src/mage/cards/w/WhispersteelDagger.java | 4 ++-- Mage.Sets/src/mage/cards/w/Wish.java | 2 +- .../CastFromGraveyardOnceEachTurnAbility.java | 2 +- Mage/src/main/java/mage/game/events/GameEvent.java | 13 ++----------- .../watchers/common/OnceEachTurnCastWatcher.java | 2 +- 31 files changed, 56 insertions(+), 66 deletions(-) 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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()); } } From 740a9347ae5628edada9f982a05679d52468a761 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 16 Nov 2024 22:12:14 +0400 Subject: [PATCH 08/12] refactor: removed some usages of short LKI, moved static ability's useable zone logic to basic ability implementation; --- .../src/mage/cards/v/VesuvanShapeshifter.java | 2 +- ...java => CommanderManaReplacementTest.java} | 2 +- .../src/main/java/mage/abilities/Ability.java | 2 ++ .../main/java/mage/abilities/AbilityImpl.java | 30 ++++++++++++------- .../java/mage/abilities/StaticAbility.java | 15 ---------- .../mage/abilities/TriggeredAbilityImpl.java | 7 +++++ .../abilities/effects/ContinuousEffects.java | 4 +-- 7 files changed, 32 insertions(+), 30 deletions(-) rename Mage.Tests/src/test/java/org/mage/test/commander/duel/{CommanderManaReplacmentTest.java => CommanderManaReplacementTest.java} (96%) 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.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/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index a3300a0092f..896cd27a1af 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -357,6 +357,8 @@ public interface Ability extends Controllable, Serializable { * - 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 source can be null for static continues effects checking like rules modification (example: Yixlid Jailer) */ boolean isInUseableZone(Game game, MageObject source, GameEvent event); diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index ecde1db6777..9afc8781cc7 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1179,6 +1179,8 @@ public abstract class AbilityImpl implements Ability { if (!this.hasSourceObjectAbility(game, source, event)) { return false; } + + // in command zone if (zone == Zone.COMMAND) { if (this.getSourceId() == null) { // commander effects return true; @@ -1199,20 +1201,18 @@ public abstract class AbilityImpl implements Ability { parameterSourceId = getSourceId(); } - // old code: - // TODO: delete after dies fix - // check against shortLKI for effects that move multiple object at the same time (e.g. destroy all) - if (game.checkShortLivingLKI(getSourceId(), getZone())) { - //return true; // fix 1 + // on entering permanents - must use static abilities like it already on battlefield + // example: Tatterkite enters without counters from Mikaeus, the Unhallowed + if (game.getPermanentEntering(parameterSourceId) != null && zone == Zone.BATTLEFIELD) { + return true; } - // 603.10. // Normally, objects that exist immediately after an event are checked to see if the event matched // any trigger conditions, and continuous effects that exist at that time are used to determine what the // trigger conditions are and what the objects involved in the event look like. // ... - Zone lookingInZone = game.getState().getZone(parameterSourceId); + Zone sourceObjectZone = game.getState().getZone(parameterSourceId); // 603.10. // ... @@ -1226,9 +1226,16 @@ public abstract class AbilityImpl implements Ability { // players can see is put into a hand or library. // TODO: research "leaves a graveyard" // TODO: research "put into a hand or library" - if (source instanceof Permanent && isTriggerCanFireAfterLeaveBattlefield(event)) { - // support leaves-the-battlefield abilities - lookingInZone = Zone.BATTLEFIELD; + if (isTriggerCanFireAfterLeaveBattlefield(event)) { + // permanents with normal triggers + if (source instanceof Permanent) { + // support leaves-the-battlefield abilities + sourceObjectZone = Zone.BATTLEFIELD; + } + // permanents with continues effects like Yixlid Jailer, see related code "isInUseableZone(game, null" + if (source == null && this instanceof StaticAbility) { + sourceObjectZone = Zone.BATTLEFIELD; + } } // TODO: research use cases and implement shared logic with "looking zone" instead LKI only @@ -1239,7 +1246,7 @@ public abstract class AbilityImpl implements Ability { // 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(lookingInZone); + return zone.match(sourceObjectZone); } public static boolean isTriggerCanFireAfterLeaveBattlefield(GameEvent event) { @@ -1255,6 +1262,7 @@ public abstract class AbilityImpl implements Ability { } 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 diff --git a/Mage/src/main/java/mage/abilities/StaticAbility.java b/Mage/src/main/java/mage/abilities/StaticAbility.java index 37ef2a1de64..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)) { // TODO: can be deleted? Need research - 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/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index d4cbc67565d..85f040de1c1 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -376,11 +376,16 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge * Kozilek card is itself and has the ability. */ + // process events from other objects Set eventTargets = CardUtil.getEventTargets(event); if (!eventTargets.contains(getSourceId())) { return super.isInUseableZone(game, source, event); } + // process events from own object + + // inject process of "look back in time" events + // TODO: need sync code with AbilityImpl.isInUseableZone switch (event.getType()) { case ZONE_CHANGE: ZoneChangeEvent zce = (ZoneChangeEvent) event; @@ -405,6 +410,8 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge } break; } + + // all other events from own object return super.isInUseableZone(game, source, 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() From 6d55e4b9e62b553ead3b14498ba3d9b04f0b8b80 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 23 Nov 2024 09:15:09 +0400 Subject: [PATCH 09/12] refactor: fixed dies events support in single cards (part 5); --- .../src/mage/cards/b/BridgeFromBelow.java | 1 + .../mage/cards/l/LyndeCheerfulTormentor.java | 1 + Mage.Sets/src/mage/cards/m/MartyrsBond.java | 6 ++ Mage.Sets/src/mage/cards/m/MolderBeast.java | 7 ++ Mage.Sets/src/mage/cards/n/NetherTraitor.java | 7 ++ .../src/mage/cards/n/NimDeathmantle.java | 7 ++ .../src/mage/cards/p/PiasRevolution.java | 7 ++ Mage.Sets/src/mage/cards/p/Psychomancer.java | 1 + Mage.Sets/src/mage/cards/p/Purgatory.java | 6 ++ Mage.Sets/src/mage/cards/s/SacredGround.java | 7 ++ Mage.Sets/src/mage/cards/s/ScrapTrawler.java | 7 ++ Mage.Sets/src/mage/cards/s/Scrapheap.java | 15 +++- .../src/mage/cards/s/SeerOfStolenSight.java | 7 ++ .../mage/cards/s/ShardOfTheVoidDragon.java | 1 + .../src/mage/cards/s/SlagstoneRefinery.java | 7 ++ .../src/mage/cards/t/TheSkullsporeNexus.java | 1 + .../src/mage/cards/t/TianaShipsCaretaker.java | 7 ++ Mage.Sets/src/mage/cards/v/ViridianRevel.java | 9 +++ .../ZoneChangeReplacementTest.java | 3 +- .../triggers/dies/SeerOfStolenSightTest.java | 81 +++++++++++++++++++ .../triggers/dies/VengefulTownsfolkTest.java | 50 +++++++++++- .../serverside/base/MageTestPlayerBase.java | 9 ++- .../java/mage/verify/VerifyCardDataTest.java | 13 ++- .../mage/abilities/TriggeredAbilityImpl.java | 12 ++- .../common/DiesOneOrMoreTriggeredAbility.java | 3 +- 25 files changed, 262 insertions(+), 13 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/SeerOfStolenSightTest.java 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/l/LyndeCheerfulTormentor.java b/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java index 3366ed15c9b..57cae92c922 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) { diff --git a/Mage.Sets/src/mage/cards/m/MartyrsBond.java b/Mage.Sets/src/mage/cards/m/MartyrsBond.java index 0eb4d9d7ad0..214e593c2a5 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class MartyrsBondEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/m/MolderBeast.java b/Mage.Sets/src/mage/cards/m/MolderBeast.java index 1346b665656..2601bac9145 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/NetherTraitor.java b/Mage.Sets/src/mage/cards/n/NetherTraitor.java index a4d9f339b6c..e2c14aee187 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/n/NimDeathmantle.java b/Mage.Sets/src/mage/cards/n/NimDeathmantle.java index 3dcefa47b45..a7e6c0124da 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class NimDeathmantleEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/p/PiasRevolution.java b/Mage.Sets/src/mage/cards/p/PiasRevolution.java index 402bc1e6ff5..ef955b74406 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, 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..70ec32afa29 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class PurgatoryExileEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/s/SacredGround.java b/Mage.Sets/src/mage/cards/s/SacredGround.java index baea68dc46d..a0df1201489 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/ScrapTrawler.java b/Mage.Sets/src/mage/cards/s/ScrapTrawler.java index ed51d7e4994..5447e2ed763 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/Scrapheap.java b/Mage.Sets/src/mage/cards/s/Scrapheap.java index a8274337aea..99f21311bed 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java b/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java index 435b71fe139..e152a0eba33 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } 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/SlagstoneRefinery.java b/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java index 635d5590fc4..4aba32355f0 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } diff --git a/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java b/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java index 530092dd1e5..a1672c71440 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) { diff --git a/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java b/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java index a3bdcbb7a44..ae1b4bea977 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } class TianaShipsCaretakerEffect extends OneShotEffect { diff --git a/Mage.Sets/src/mage/cards/v/ViridianRevel.java b/Mage.Sets/src/mage/cards/v/ViridianRevel.java index 79212a3654d..6821fc5ec76 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 source, GameEvent event) { + return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); + } } \ No newline at end of file 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/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/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index c5e1d336f16..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 @@ -351,8 +351,15 @@ public abstract class MageTestPlayerBase { * Add target destroy ability that can be called by text "target destroy" */ 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, diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 19782eb047e..797e66bb201 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -1990,12 +1990,21 @@ public class VerifyCardDataTest { .filter(a -> a instanceof TriggeredAbility) .map(a -> (TriggeredAbility) a) .filter(a -> !a.isLeavesTheBattlefieldTrigger()) - .filter(a -> a.getRule().contains("whenever") || a.getRule().contains("Whenever")) - .filter(a -> a.getRule().contains("dies")) + //.filter(a -> a.getRule().contains("whenever") || a.getRule().contains("Whenever")) // TODO: research failed cards + .filter(a -> a.getRule().contains("die ") + || a.getRule().contains("dies ") + || a.getRule().contains("die,") + || a.getRule().contains("dies,") + || (a.getRule().contains("put into") + && a.getRule().contains("graveyard") + && a.getRule().contains("from the battlefield")) + ) + .filter(a -> !a.getRule().contains("roll")) // ignore roll die effects .filter(a -> !a.getRule().contains("with \"When")) // ignore token creating effects .filter(a -> !a.getRule().contains("gains \"When")) // ignore token creating effects .filter(a -> !a.getRule().contains("and \"When")) // ignore token creating effects .filter(a -> !a.getRule().contains("dies while {this} is in your graveyard")) // ignore Boneyard Scourge + .filter(a -> !a.getRule().contains("all creature cards that were put into your")) // ignore Fell Shepherd .filter(a -> !card.getName().equals("Massacre Girl") // delayed trigger fixed, but verify check can't find it && !card.getName().equals("Infested Thrinax") && !card.getName().equals("Xira, the Golden Sting") diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index 85f040de1c1..cb78d81efd1 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; @@ -465,8 +467,14 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge public static boolean isInUseableZoneDiesTrigger(TriggeredAbility source, GameEvent event, Game game) { // runtime check: wrong trigger settings if (!source.isLeavesTheBattlefieldTrigger()) { - // TODO: enable after fix - // throw new IllegalArgumentException("Wrong code usage: all dies triggers must use setLeavesTheBattlefieldTrigger(true)"); + throw new IllegalArgumentException("Wrong code usage: all dies triggers must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + + source.getSourceObject(game) + " - " + source); + } + + // 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 - " + + source.getSourceObject(game) + " - " + source); } // Get the source permanent of the ability diff --git a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java index 655a7d57839..5a4a8df4de2 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) { @@ -59,6 +60,6 @@ public class DiesOneOrMoreTriggeredAbility extends TriggeredAbilityImpl implemen return ((ZoneChangeBatchEvent) event) .getEvents() .stream() - .allMatch(e -> TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, e, game)); + .anyMatch(e -> TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, e, game)); } } From 57ef74da9001e6b76848617f2888adf857111877 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 30 Nov 2024 03:19:24 +0400 Subject: [PATCH 10/12] Recover abilities - fixed that it doesn't ask to pay a cost on multiple triggers; --- .../cards/abilities/keywords/ExploitTest.java | 5 +- .../cards/abilities/keywords/RecoverTest.java | 115 +++++++++++++++++- .../abilities/keyword/RecoverAbility.java | 38 ++---- 3 files changed, 120 insertions(+), 38 deletions(-) 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/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/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"); From d49ff89a81fa11d83c2a797c3a687f18e868804a Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 30 Nov 2024 03:26:03 +0400 Subject: [PATCH 11/12] refactor: shared logic for diff implementation of isInUseableZone, improved docs and readability; --- .../src/mage/cards/a/AjanisLastStand.java | 4 +- .../src/mage/cards/a/AthreosGodOfPassage.java | 4 +- Mage.Sets/src/mage/cards/a/AvacynsCollar.java | 4 +- Mage.Sets/src/mage/cards/b/Bereavement.java | 4 +- Mage.Sets/src/mage/cards/d/DeathTyrant.java | 4 +- .../src/mage/cards/d/DeathsPresence.java | 4 +- .../src/mage/cards/d/DiregrafCaptain.java | 4 +- .../src/mage/cards/f/FalkenrathNoble.java | 4 +- Mage.Sets/src/mage/cards/g/GraveBetrayal.java | 4 +- Mage.Sets/src/mage/cards/g/GravePact.java | 4 +- Mage.Sets/src/mage/cards/g/GutterGrime.java | 4 +- .../src/mage/cards/h/HatefulEidolon.java | 4 +- .../src/mage/cards/i/InfestedThrinax.java | 4 +- .../src/mage/cards/l/LuminousBroodmoth.java | 4 +- .../mage/cards/l/LyndeCheerfulTormentor.java | 4 +- .../mage/cards/m/MarchesaTheBlackRose.java | 4 +- .../src/mage/cards/m/MariTheKillingQuill.java | 4 +- Mage.Sets/src/mage/cards/m/MartyrsBond.java | 4 +- Mage.Sets/src/mage/cards/m/MassacreGirl.java | 4 +- Mage.Sets/src/mage/cards/m/MassacreWurm.java | 4 +- .../cards/m/MillicentRestlessRevenant.java | 6 +- Mage.Sets/src/mage/cards/m/MimicVat.java | 4 +- Mage.Sets/src/mage/cards/m/MolderBeast.java | 4 +- .../src/mage/cards/m/MycoidShepherd.java | 4 +- Mage.Sets/src/mage/cards/n/Necroskitter.java | 4 +- Mage.Sets/src/mage/cards/n/NetherTraitor.java | 4 +- .../src/mage/cards/n/NimDeathmantle.java | 4 +- .../mage/cards/o/OrahSkyclaveHierophant.java | 4 +- .../src/mage/cards/p/PatronOfTheVein.java | 4 +- .../src/mage/cards/p/PiasRevolution.java | 4 +- Mage.Sets/src/mage/cards/p/ProperBurial.java | 4 +- Mage.Sets/src/mage/cards/p/Purgatory.java | 4 +- Mage.Sets/src/mage/cards/r/Remembrance.java | 4 +- .../src/mage/cards/r/RhukHexgoldNabber.java | 6 +- .../mage/cards/r/RienneAngelOfRebirth.java | 4 +- Mage.Sets/src/mage/cards/s/SacredGround.java | 4 +- Mage.Sets/src/mage/cards/s/Sangromancer.java | 4 +- Mage.Sets/src/mage/cards/s/ScrapTrawler.java | 4 +- Mage.Sets/src/mage/cards/s/Scrapheap.java | 4 +- .../src/mage/cards/s/SeerOfStolenSight.java | 4 +- .../mage/cards/s/ShelobChildOfUngoliant.java | 4 +- .../src/mage/cards/s/SlagstoneRefinery.java | 4 +- Mage.Sets/src/mage/cards/s/SlayersPlate.java | 4 +- Mage.Sets/src/mage/cards/s/Sporogenesis.java | 4 +- .../src/mage/cards/s/SyrKonradTheGrim.java | 4 +- .../src/mage/cards/t/TheScorpionGod.java | 4 +- .../src/mage/cards/t/TheSkullsporeNexus.java | 4 +- .../src/mage/cards/t/TianaShipsCaretaker.java | 4 +- .../src/mage/cards/v/VerdantSuccession.java | 4 +- .../src/mage/cards/v/VillageCannibals.java | 4 +- .../src/mage/cards/v/VindictiveVampire.java | 4 +- Mage.Sets/src/mage/cards/v/ViridianRevel.java | 4 +- .../src/mage/cards/x/XiraTheGoldenSting.java | 4 +- .../src/main/java/mage/abilities/Ability.java | 11 +- .../main/java/mage/abilities/AbilityImpl.java | 85 +++++----- .../mage/abilities/TriggeredAbilities.java | 2 +- .../java/mage/abilities/TriggeredAbility.java | 9 + .../mage/abilities/TriggeredAbilityImpl.java | 154 +++++++++--------- .../DealtDamageAndDiedTriggeredAbility.java | 4 +- ...DamageAttachedAndDiedTriggeredAbility.java | 4 +- .../common/DiesAttachedTriggeredAbility.java | 4 +- .../common/DiesCreatureTriggeredAbility.java | 6 +- .../common/DiesOneOrMoreTriggeredAbility.java | 4 +- .../common/DiesSourceTriggeredAbility.java | 4 +- .../DiesThisOrAnotherTriggeredAbility.java | 4 +- ...aveFromAnywhereSourceTriggeredAbility.java | 2 +- ...aveFromBattlefieldAllTriggeredAbility.java | 4 +- ...FromBattlefieldSourceTriggeredAbility.java | 4 +- ...ilYourNextTurnDelayedTriggeredAbility.java | 6 +- ...ditionalInterveningIfTriggeredAbility.java | 6 +- .../ConditionalTriggeredAbility.java | 6 +- .../mage/abilities/keyword/HauntAbility.java | 2 +- .../abilities/meta/OrTriggeredAbility.java | 6 +- .../main/java/mage/designations/Monarch.java | 4 +- .../mage/game/events/ZoneChangeEvent.java | 7 +- .../java/mage/game/stack/StackAbility.java | 4 +- 76 files changed, 285 insertions(+), 273 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AjanisLastStand.java b/Mage.Sets/src/mage/cards/a/AjanisLastStand.java index f8c85f68c8b..a2697f89c90 100644 --- a/Mage.Sets/src/mage/cards/a/AjanisLastStand.java +++ b/Mage.Sets/src/mage/cards/a/AjanisLastStand.java @@ -103,7 +103,7 @@ class AjanisLastStandTriggeredAbility 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/a/AthreosGodOfPassage.java b/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java index 4f489a953a8..33c317738ce 100644 --- a/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java +++ b/Mage.Sets/src/mage/cards/a/AthreosGodOfPassage.java @@ -157,7 +157,7 @@ class AthreosDiesCreatureTriggeredAbility 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/a/AvacynsCollar.java b/Mage.Sets/src/mage/cards/a/AvacynsCollar.java index bdbbf64e78c..1c6baec7753 100644 --- a/Mage.Sets/src/mage/cards/a/AvacynsCollar.java +++ b/Mage.Sets/src/mage/cards/a/AvacynsCollar.java @@ -95,7 +95,7 @@ class AvacynsCollarTriggeredAbility 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/b/Bereavement.java b/Mage.Sets/src/mage/cards/b/Bereavement.java index cb2389b6502..05fb4ff62c1 100644 --- a/Mage.Sets/src/mage/cards/b/Bereavement.java +++ b/Mage.Sets/src/mage/cards/b/Bereavement.java @@ -78,7 +78,7 @@ class BereavementTriggeredAbility 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/DeathTyrant.java b/Mage.Sets/src/mage/cards/d/DeathTyrant.java index 0a670188b4c..18d0feed6a4 100644 --- a/Mage.Sets/src/mage/cards/d/DeathTyrant.java +++ b/Mage.Sets/src/mage/cards/d/DeathTyrant.java @@ -95,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 c4b7e395008..8ccfce29cac 100644 --- a/Mage.Sets/src/mage/cards/d/DeathsPresence.java +++ b/Mage.Sets/src/mage/cards/d/DeathsPresence.java @@ -82,7 +82,7 @@ class DeathsPresenceTriggeredAbility 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/DiregrafCaptain.java b/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java index b68be216556..9db8a67e7ee 100644 --- a/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java +++ b/Mage.Sets/src/mage/cards/d/DiregrafCaptain.java @@ -103,7 +103,7 @@ class DiregrafCaptainTriggeredAbility 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); } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java b/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java index ca82f007fd5..01cae39b36b 100644 --- a/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java +++ b/Mage.Sets/src/mage/cards/f/FalkenrathNoble.java @@ -92,7 +92,7 @@ class FalkenrathNobleTriggeredAbility 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/g/GraveBetrayal.java b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java index 74d02b03726..4d70006e4cc 100644 --- a/Mage.Sets/src/mage/cards/g/GraveBetrayal.java +++ b/Mage.Sets/src/mage/cards/g/GraveBetrayal.java @@ -97,8 +97,8 @@ class GraveBetrayalTriggeredAbility 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/g/GravePact.java b/Mage.Sets/src/mage/cards/g/GravePact.java index 1fd5127bd28..4c5a036722d 100644 --- a/Mage.Sets/src/mage/cards/g/GravePact.java +++ b/Mage.Sets/src/mage/cards/g/GravePact.java @@ -77,8 +77,8 @@ class GravePactTriggeredAbility 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/g/GutterGrime.java b/Mage.Sets/src/mage/cards/g/GutterGrime.java index a5044904968..956f177edff 100644 --- a/Mage.Sets/src/mage/cards/g/GutterGrime.java +++ b/Mage.Sets/src/mage/cards/g/GutterGrime.java @@ -86,8 +86,8 @@ class GutterGrimeTriggeredAbility 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/h/HatefulEidolon.java b/Mage.Sets/src/mage/cards/h/HatefulEidolon.java index 4f92ff17ce7..3a0e4298251 100644 --- a/Mage.Sets/src/mage/cards/h/HatefulEidolon.java +++ b/Mage.Sets/src/mage/cards/h/HatefulEidolon.java @@ -109,7 +109,7 @@ class HatefulEidolonTriggeredAbility 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/i/InfestedThrinax.java b/Mage.Sets/src/mage/cards/i/InfestedThrinax.java index a8c84cd0b61..a4c24be192a 100644 --- a/Mage.Sets/src/mage/cards/i/InfestedThrinax.java +++ b/Mage.Sets/src/mage/cards/i/InfestedThrinax.java @@ -94,7 +94,7 @@ class InfestedThrinaxTriggeredAbility extends DelayedTriggeredAbility { } @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/l/LuminousBroodmoth.java b/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java index 9b28106c602..916f03d6777 100644 --- a/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java +++ b/Mage.Sets/src/mage/cards/l/LuminousBroodmoth.java @@ -101,8 +101,8 @@ class LuminousBroodmothTriggeredAbility 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/l/LyndeCheerfulTormentor.java b/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java index 57cae92c922..4bb11c18f64 100644 --- a/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java +++ b/Mage.Sets/src/mage/cards/l/LyndeCheerfulTormentor.java @@ -103,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/MarchesaTheBlackRose.java b/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java index 8ec3e3ea3cc..a2593ee86a9 100644 --- a/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java +++ b/Mage.Sets/src/mage/cards/m/MarchesaTheBlackRose.java @@ -104,8 +104,8 @@ class MarchesaTheBlackRoseTriggeredAbility 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/m/MariTheKillingQuill.java b/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java index a7212e18e92..be800e48089 100644 --- a/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java +++ b/Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java @@ -206,8 +206,8 @@ class MariTheKillingQuillCreatureDiesAbility 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/m/MartyrsBond.java b/Mage.Sets/src/mage/cards/m/MartyrsBond.java index 214e593c2a5..7183de40072 100644 --- a/Mage.Sets/src/mage/cards/m/MartyrsBond.java +++ b/Mage.Sets/src/mage/cards/m/MartyrsBond.java @@ -91,8 +91,8 @@ class MartyrsBondTriggeredAbility 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/m/MassacreGirl.java b/Mage.Sets/src/mage/cards/m/MassacreGirl.java index 10be05d22dd..fa5de8db62e 100644 --- a/Mage.Sets/src/mage/cards/m/MassacreGirl.java +++ b/Mage.Sets/src/mage/cards/m/MassacreGirl.java @@ -108,7 +108,7 @@ class MassacreGirlDelayedTriggeredAbility extends DelayedTriggeredAbility { } @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); } } \ 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 fed541ad199..ef0fde681c5 100644 --- a/Mage.Sets/src/mage/cards/m/MassacreWurm.java +++ b/Mage.Sets/src/mage/cards/m/MassacreWurm.java @@ -85,7 +85,7 @@ class MassacreWurmTriggeredAbility 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/m/MillicentRestlessRevenant.java b/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java index 7f2dba51de3..a69602dea49 100644 --- a/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java +++ b/Mage.Sets/src/mage/cards/m/MillicentRestlessRevenant.java @@ -125,11 +125,11 @@ class MillicentRestlessRevenantTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { - 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.Sets/src/mage/cards/m/MimicVat.java b/Mage.Sets/src/mage/cards/m/MimicVat.java index 4c42f835460..8d7a377ec93 100644 --- a/Mage.Sets/src/mage/cards/m/MimicVat.java +++ b/Mage.Sets/src/mage/cards/m/MimicVat.java @@ -110,8 +110,8 @@ class MimicVatTriggeredAbility 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/m/MolderBeast.java b/Mage.Sets/src/mage/cards/m/MolderBeast.java index 2601bac9145..696f0ad2c06 100644 --- a/Mage.Sets/src/mage/cards/m/MolderBeast.java +++ b/Mage.Sets/src/mage/cards/m/MolderBeast.java @@ -78,7 +78,7 @@ class MolderBeastTriggeredAbility 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); } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/m/MycoidShepherd.java b/Mage.Sets/src/mage/cards/m/MycoidShepherd.java index 4a1ff8a742c..51f3d1f815b 100644 --- a/Mage.Sets/src/mage/cards/m/MycoidShepherd.java +++ b/Mage.Sets/src/mage/cards/m/MycoidShepherd.java @@ -94,7 +94,7 @@ class MycoidShepherdTriggeredAbility 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); } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/Necroskitter.java b/Mage.Sets/src/mage/cards/n/Necroskitter.java index a1f620abf7e..dc9742b15c4 100644 --- a/Mage.Sets/src/mage/cards/n/Necroskitter.java +++ b/Mage.Sets/src/mage/cards/n/Necroskitter.java @@ -96,7 +96,7 @@ class NecroskitterTriggeredAbility 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/n/NetherTraitor.java b/Mage.Sets/src/mage/cards/n/NetherTraitor.java index e2c14aee187..3b4f04d605d 100644 --- a/Mage.Sets/src/mage/cards/n/NetherTraitor.java +++ b/Mage.Sets/src/mage/cards/n/NetherTraitor.java @@ -98,7 +98,7 @@ class NetherTraitorTriggeredAbility 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/n/NimDeathmantle.java b/Mage.Sets/src/mage/cards/n/NimDeathmantle.java index a7e6c0124da..3dca6eb6eeb 100644 --- a/Mage.Sets/src/mage/cards/n/NimDeathmantle.java +++ b/Mage.Sets/src/mage/cards/n/NimDeathmantle.java @@ -112,8 +112,8 @@ class NimDeathmantleTriggeredAbility 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/o/OrahSkyclaveHierophant.java b/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java index 26d3c229d5c..96a82a5c3e8 100644 --- a/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java +++ b/Mage.Sets/src/mage/cards/o/OrahSkyclaveHierophant.java @@ -98,7 +98,7 @@ class OrahSkyclaveHierophantTriggeredAbility 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/p/PatronOfTheVein.java b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java index 0d94c5d9249..7460ec51914 100644 --- a/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java +++ b/Mage.Sets/src/mage/cards/p/PatronOfTheVein.java @@ -110,8 +110,8 @@ class PatronOfTheVeinCreatureDiesTriggeredAbility 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/p/PiasRevolution.java b/Mage.Sets/src/mage/cards/p/PiasRevolution.java index ef955b74406..cf9897d2572 100644 --- a/Mage.Sets/src/mage/cards/p/PiasRevolution.java +++ b/Mage.Sets/src/mage/cards/p/PiasRevolution.java @@ -131,7 +131,7 @@ class PiasRevolutionTriggeredAbility 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/p/ProperBurial.java b/Mage.Sets/src/mage/cards/p/ProperBurial.java index fab4cee4729..0e46e0e563a 100644 --- a/Mage.Sets/src/mage/cards/p/ProperBurial.java +++ b/Mage.Sets/src/mage/cards/p/ProperBurial.java @@ -80,7 +80,7 @@ class ProperBurialTriggeredAbility 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/p/Purgatory.java b/Mage.Sets/src/mage/cards/p/Purgatory.java index 70ec32afa29..295053af5f3 100644 --- a/Mage.Sets/src/mage/cards/p/Purgatory.java +++ b/Mage.Sets/src/mage/cards/p/Purgatory.java @@ -106,8 +106,8 @@ class PurgatoryTriggeredAbility 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/r/Remembrance.java b/Mage.Sets/src/mage/cards/r/Remembrance.java index b510e445d34..a9fe28c4235 100644 --- a/Mage.Sets/src/mage/cards/r/Remembrance.java +++ b/Mage.Sets/src/mage/cards/r/Remembrance.java @@ -96,7 +96,7 @@ class RemembranceTriggeredAbility 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/r/RhukHexgoldNabber.java b/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java index 49c8106d498..3bce95e4211 100644 --- a/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java +++ b/Mage.Sets/src/mage/cards/r/RhukHexgoldNabber.java @@ -114,11 +114,11 @@ class RhukHexgoldNabberTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { - 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.Sets/src/mage/cards/r/RienneAngelOfRebirth.java b/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java index f0aca8d28d7..850fb69a03f 100644 --- a/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java +++ b/Mage.Sets/src/mage/cards/r/RienneAngelOfRebirth.java @@ -113,8 +113,8 @@ class RienneAngelOfRebirthTriggeredAbility 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/s/SacredGround.java b/Mage.Sets/src/mage/cards/s/SacredGround.java index a0df1201489..7fa043d7480 100644 --- a/Mage.Sets/src/mage/cards/s/SacredGround.java +++ b/Mage.Sets/src/mage/cards/s/SacredGround.java @@ -79,7 +79,7 @@ class SacredGroundTriggeredAbility 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/s/Sangromancer.java b/Mage.Sets/src/mage/cards/s/Sangromancer.java index 84cd3e50487..90f62fec037 100644 --- a/Mage.Sets/src/mage/cards/s/Sangromancer.java +++ b/Mage.Sets/src/mage/cards/s/Sangromancer.java @@ -80,8 +80,8 @@ class SangromancerFirstTriggeredAbility 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/s/ScrapTrawler.java b/Mage.Sets/src/mage/cards/s/ScrapTrawler.java index 5447e2ed763..80078d64cbe 100644 --- a/Mage.Sets/src/mage/cards/s/ScrapTrawler.java +++ b/Mage.Sets/src/mage/cards/s/ScrapTrawler.java @@ -94,7 +94,7 @@ class ScrapTrawlerTriggeredAbility 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/s/Scrapheap.java b/Mage.Sets/src/mage/cards/s/Scrapheap.java index 99f21311bed..013e4aa5aaf 100644 --- a/Mage.Sets/src/mage/cards/s/Scrapheap.java +++ b/Mage.Sets/src/mage/cards/s/Scrapheap.java @@ -80,7 +80,7 @@ class ScrapheapTriggeredAbility 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/s/SeerOfStolenSight.java b/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java index e152a0eba33..86ec5fb253f 100644 --- a/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java +++ b/Mage.Sets/src/mage/cards/s/SeerOfStolenSight.java @@ -89,7 +89,7 @@ class SeerOfStolenSightTriggeredAbility 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/s/ShelobChildOfUngoliant.java b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java index 8bf7976f1fa..51cd8a005ce 100644 --- a/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java +++ b/Mage.Sets/src/mage/cards/s/ShelobChildOfUngoliant.java @@ -189,8 +189,8 @@ class ShelobChildOfUngoliantTriggeredAbility 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/s/SlagstoneRefinery.java b/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java index 4aba32355f0..53cee240701 100644 --- a/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java +++ b/Mage.Sets/src/mage/cards/s/SlagstoneRefinery.java @@ -83,7 +83,7 @@ class SlagstoneRefineryTriggeredAbility 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/s/SlayersPlate.java b/Mage.Sets/src/mage/cards/s/SlayersPlate.java index c102ddfd9e8..56e9742fb9e 100644 --- a/Mage.Sets/src/mage/cards/s/SlayersPlate.java +++ b/Mage.Sets/src/mage/cards/s/SlayersPlate.java @@ -92,7 +92,7 @@ class SlayersPlateTriggeredAbility 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/s/Sporogenesis.java b/Mage.Sets/src/mage/cards/s/Sporogenesis.java index ddbf72aa1b9..61960d3cf7c 100644 --- a/Mage.Sets/src/mage/cards/s/Sporogenesis.java +++ b/Mage.Sets/src/mage/cards/s/Sporogenesis.java @@ -108,8 +108,8 @@ class SporogenesisTriggeredAbility 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/s/SyrKonradTheGrim.java b/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java index 050f9e5c5b6..fad8498bc0b 100644 --- a/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java +++ b/Mage.Sets/src/mage/cards/s/SyrKonradTheGrim.java @@ -96,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/TheScorpionGod.java b/Mage.Sets/src/mage/cards/t/TheScorpionGod.java index 6e50ba4cbdf..8a03884f9e0 100644 --- a/Mage.Sets/src/mage/cards/t/TheScorpionGod.java +++ b/Mage.Sets/src/mage/cards/t/TheScorpionGod.java @@ -109,8 +109,8 @@ class TheScorpionGodTriggeredAbility 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/TheSkullsporeNexus.java b/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java index a1672c71440..ff220b1df5a 100644 --- a/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java +++ b/Mage.Sets/src/mage/cards/t/TheSkullsporeNexus.java @@ -171,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 ae1b4bea977..658f2bfb8c2 100644 --- a/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java +++ b/Mage.Sets/src/mage/cards/t/TianaShipsCaretaker.java @@ -102,8 +102,8 @@ class TianaShipsCaretakerTriggeredAbility 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/v/VerdantSuccession.java b/Mage.Sets/src/mage/cards/v/VerdantSuccession.java index 61f8de91be2..3e0b602b9e2 100644 --- a/Mage.Sets/src/mage/cards/v/VerdantSuccession.java +++ b/Mage.Sets/src/mage/cards/v/VerdantSuccession.java @@ -101,8 +101,8 @@ class VerdantSuccessionTriggeredAbility 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/v/VillageCannibals.java b/Mage.Sets/src/mage/cards/v/VillageCannibals.java index be700813d5b..9fb2dab04a7 100644 --- a/Mage.Sets/src/mage/cards/v/VillageCannibals.java +++ b/Mage.Sets/src/mage/cards/v/VillageCannibals.java @@ -78,7 +78,7 @@ class VillageCannibalsTriggeredAbility 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/v/VindictiveVampire.java b/Mage.Sets/src/mage/cards/v/VindictiveVampire.java index 1a6f257b23a..34af2943e23 100644 --- a/Mage.Sets/src/mage/cards/v/VindictiveVampire.java +++ b/Mage.Sets/src/mage/cards/v/VindictiveVampire.java @@ -70,8 +70,8 @@ class VindictiveVampireTriggeredAbility 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/v/ViridianRevel.java b/Mage.Sets/src/mage/cards/v/ViridianRevel.java index 6821fc5ec76..9606f89b43e 100644 --- a/Mage.Sets/src/mage/cards/v/ViridianRevel.java +++ b/Mage.Sets/src/mage/cards/v/ViridianRevel.java @@ -81,7 +81,7 @@ class ViridianRevelTriggeredAbility 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); } } \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java b/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java index f23f6b45cbb..e94568dec23 100644 --- a/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java +++ b/Mage.Sets/src/mage/cards/x/XiraTheGoldenSting.java @@ -105,7 +105,7 @@ class XiraTheGoldenStingTriggeredAbility extends WhenTargetDiesDelayedTriggeredA } @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/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index 896cd27a1af..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(); @@ -358,16 +355,17 @@ public interface Ability extends Controllable, Serializable { * - for leave battlefield triggers - keep default + set setLeavesTheBattlefieldTrigger(true) * - for dies triggers - override and use TriggeredAbilityImpl.isInUseableZoneDiesTrigger inside + set setLeavesTheBattlefieldTrigger(true) * - * @param source can be null for static continues effects checking like rules modification (example: Yixlid Jailer) + * @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 @@ -485,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 9afc8781cc7..ea7ea5d55ef 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1175,35 +1175,31 @@ public abstract class AbilityImpl implements Ability { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - if (!this.hasSourceObjectAbility(game, source, event)) { + public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { + if (!this.hasSourceObjectAbility(game, sourceObject, event)) { return false; } + // workaround for singleton abilities like Flying + UUID affectedSourceId = getRealSourceObjectId(this, sourceObject); + // in command zone if (zone == Zone.COMMAND) { - if (this.getSourceId() == null) { // commander effects + if (affectedSourceId == null) { + // commander effects return true; + } else { + MageObject object = game.getObject(affectedSourceId); + // emblem/planes are always actual + if (object instanceof Emblem || object instanceof Dungeon || object instanceof Plane) { + return true; + } } - MageObject object = game.getObject(this.getSourceId()); - // emblem/planes are always actual - if (object instanceof Emblem || object instanceof Dungeon || object instanceof Plane) { - return true; - } - } - - 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(); } // on entering permanents - must use static abilities like it already on battlefield // example: Tatterkite enters without counters from Mikaeus, the Unhallowed - if (game.getPermanentEntering(parameterSourceId) != null && zone == Zone.BATTLEFIELD) { + if (game.getPermanentEntering(affectedSourceId) != null && zone == Zone.BATTLEFIELD) { return true; } @@ -1212,7 +1208,7 @@ public abstract class AbilityImpl implements Ability { // 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(parameterSourceId); + Zone sourceObjectZone = game.getState().getZone(affectedSourceId); // 603.10. // ... @@ -1228,12 +1224,12 @@ public abstract class AbilityImpl implements Ability { // TODO: research "put into a hand or library" if (isTriggerCanFireAfterLeaveBattlefield(event)) { // permanents with normal triggers - if (source instanceof Permanent) { + 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 (source == null && this instanceof StaticAbility) { + if (sourceObject == null && this instanceof StaticAbility) { sourceObjectZone = Zone.BATTLEFIELD; } } @@ -1267,6 +1263,7 @@ public abstract class AbilityImpl implements Ability { // 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: @@ -1279,31 +1276,43 @@ public abstract class AbilityImpl implements Ability { }); } - @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 + /** + * 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 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/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 61c60ae9b31..f47a10f76c6 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbility.java @@ -72,6 +72,15 @@ public interface TriggeredAbility extends Ability { * 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); diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index cb78d81efd1..a119c31c1bb 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -348,73 +348,61 @@ 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); - // process events from other objects - 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. - // process events from own object + // There are possible two different use cases: + // * look in current game state (normal events): + // * look back in time (leaves battlefield, dies, etc); - // inject process of "look back in time" events - // TODO: need sync code with AbilityImpl.isInUseableZone - 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; + // 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: - case EXPLOITED_CREATURE: - 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; + } } - // all other events from own object - return super.isInUseableZone(game, source, event); + return super.isInUseableZone(game, affectedSourceObject, event); } @Override @@ -464,23 +452,26 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge * (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) { + public static boolean isInUseableZoneDiesTrigger(TriggeredAbility sourceAbility, MageObject sourceObject, GameEvent event, Game game) { // runtime check: wrong trigger settings - if (!source.isLeavesTheBattlefieldTrigger()) { + if (!sourceAbility.isLeavesTheBattlefieldTrigger()) { throw new IllegalArgumentException("Wrong code usage: all dies triggers must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " - + source.getSourceObject(game) + " - " + source); + + 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 - " - + source.getSourceObject(game) + " - " + source); + + sourceAbility.getSourceObject(game) + " - " + sourceAbility); } - // Get the source permanent of the ability - MageObject sourceObject = null; - if (game.getState().getZone(source.getSourceId()) == Zone.BATTLEFIELD) { - sourceObject = game.getPermanent(source.getSourceId()); + // 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 // - @@ -498,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 = 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/DealtDamageAndDiedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java index 66efee58cfe..6afca9b9c41 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAndDiedTriggeredAbility.java @@ -85,7 +85,7 @@ public class DealtDamageAndDiedTriggeredAbility 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/DealtDamageAttachedAndDiedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java index e0c3e7de9de..04a36d16338 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedAndDiedTriggeredAbility.java @@ -79,7 +79,7 @@ public class DealtDamageAttachedAndDiedTriggeredAbility extends TriggeredAbility } @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/DiesAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java index 2befcc668bc..b291486ec39 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesAttachedTriggeredAbility.java @@ -161,7 +161,7 @@ public class DiesAttachedTriggeredAbility 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/DiesCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java index 421d95bd9be..9b885b1bf6e 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesCreatureTriggeredAbility.java @@ -82,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 5a4a8df4de2..a537a79f359 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesOneOrMoreTriggeredAbility.java @@ -56,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() - .anyMatch(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 96851179e44..e55160ee720 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java @@ -66,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/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 f6e12c78806..f67a6bdba61 100644 --- a/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/PutIntoGraveFromBattlefieldAllTriggeredAbility.java @@ -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/delayed/UntilYourNextTurnDelayedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java index 74950868014..33f9a066f40 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/UntilYourNextTurnDelayedTriggeredAbility.java @@ -107,12 +107,12 @@ public class UntilYourNextTurnDelayedTriggeredAbility extends DelayedTriggeredAb } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + 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, 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/decorator/ConditionalInterveningIfTriggeredAbility.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java index 68a75a9274f..ab4e86f5d4d 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalInterveningIfTriggeredAbility.java @@ -137,12 +137,12 @@ public class ConditionalInterveningIfTriggeredAbility extends TriggeredAbilityIm } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + 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, 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/decorator/ConditionalTriggeredAbility.java b/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java index c6aad85b6bf..4a2b89ce8f5 100644 --- a/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/decorator/ConditionalTriggeredAbility.java @@ -123,12 +123,12 @@ public class ConditionalTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + 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, 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/keyword/HauntAbility.java b/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java index c6343ba97e8..c35e7592b21 100644 --- a/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java @@ -128,7 +128,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/meta/OrTriggeredAbility.java b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java index b79d1b84210..2342564f662 100644 --- a/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/meta/OrTriggeredAbility.java @@ -127,15 +127,15 @@ public class OrTriggeredAbility extends TriggeredAbilityImpl { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + 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, event, game); + res |= TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); } else { - res |= super.isInUseableZone(game, source, event); + 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/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/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."); } From b1024d23fca7ac5a97348065b6cd50dc2b691573 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 30 Nov 2024 16:56:00 +0400 Subject: [PATCH 12/12] refactor: fixed dies events support in single cards (part 6); --- .../src/mage/cards/c/CallerOfTheClaw.java | 2 +- .../src/mage/cards/d/DiabolicServitude.java | 8 ++ Mage.Sets/src/mage/cards/e/EndlessEvil.java | 7 ++ Mage.Sets/src/mage/cards/e/EnigmaSphinx.java | 7 ++ .../src/mage/cards/k/KayasGhostform.java | 1 + .../src/mage/cards/m/MagusOfTheBridge.java | 7 ++ .../cards/t/TaekoThePatientAvalanche.java | 1 + .../java/mage/verify/VerifyCardDataTest.java | 73 ++++++++++++------- .../main/java/mage/abilities/AbilityImpl.java | 2 +- .../mage/abilities/keyword/HauntAbility.java | 1 + 10 files changed, 80 insertions(+), 29 deletions(-) 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/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/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/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/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/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.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 797e66bb201..e018ec86f55 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -1985,33 +1985,52 @@ public class VerifyCardDataTest { fail(card, "abilities", "mutate cards aren't implemented and shouldn't be available"); } - // special check: wrong dies triggers - card.getAbilities().stream() - .filter(a -> a instanceof TriggeredAbility) - .map(a -> (TriggeredAbility) a) - .filter(a -> !a.isLeavesTheBattlefieldTrigger()) - //.filter(a -> a.getRule().contains("whenever") || a.getRule().contains("Whenever")) // TODO: research failed cards - .filter(a -> a.getRule().contains("die ") - || a.getRule().contains("dies ") - || a.getRule().contains("die,") - || a.getRule().contains("dies,") - || (a.getRule().contains("put into") - && a.getRule().contains("graveyard") - && a.getRule().contains("from the battlefield")) - ) - .filter(a -> !a.getRule().contains("roll")) // ignore roll die effects - .filter(a -> !a.getRule().contains("with \"When")) // ignore token creating effects - .filter(a -> !a.getRule().contains("gains \"When")) // ignore token creating effects - .filter(a -> !a.getRule().contains("and \"When")) // ignore token creating effects - .filter(a -> !a.getRule().contains("dies while {this} is in your graveyard")) // ignore Boneyard Scourge - .filter(a -> !a.getRule().contains("all creature cards that were put into your")) // ignore Fell Shepherd - .filter(a -> !card.getName().equals("Massacre Girl") // delayed trigger fixed, but verify check can't find it - && !card.getName().equals("Infested Thrinax") - && !card.getName().equals("Xira, the Golden Sting") - ) - .forEach(a -> { - fail(card, "abilities", "dies trigger must use setLeavesTheBattlefieldTrigger(true) and override isInUseableZone - " + a.getClass().getSimpleName()); - }); + // 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 diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index ea7ea5d55ef..214b7b015a9 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1290,7 +1290,7 @@ public abstract class AbilityImpl implements Ability { } @Override - public boolean hasSourceObjectAbility(Game game, MageObject sourceObject, GameEvent event) { + public final boolean hasSourceObjectAbility(Game game, MageObject sourceObject, GameEvent event) { MageObject object = sourceObject; if (object == null) { object = game.getPermanentEntering(getSourceId()); diff --git a/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java b/Mage/src/main/java/mage/abilities/keyword/HauntAbility.java index c35e7592b21..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) {