From 7ae61944f52866d83a66df8de0a98d768e905a9e Mon Sep 17 00:00:00 2001 From: spjspj Date: Sun, 10 Nov 2024 22:05:25 +1100 Subject: [PATCH 01/40] Update ChatPanelBasic.java --- Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java b/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java index acf12357f10..3e9158d6ebc 100644 --- a/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java +++ b/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java @@ -265,7 +265,7 @@ public class ChatPanelBasic extends javax.swing.JPanel { } String cachedProfanityFilterValue = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GAME_USE_PROFANITY_FILTER, "0"); - boolean isContainsSwearing = !containsSwearing(messageToTest, cachedProfanityFilterValue); + boolean isContainsSwearing = containsSwearing(messageToTest, cachedProfanityFilterValue); boolean isUserInfoOrGameOrStatus = messageType == MessageType.USER_INFO || messageType == MessageType.GAME || messageType == MessageType.STATUS; if (isUserInfoOrGameOrStatus || cachedProfanityFilterValue.equals("0") || (!cachedProfanityFilterValue.equals("0") && !isContainsSwearing)) { if (username != null && !username.isEmpty()) { -- 2.47.2 From 66b338c6fccb760b3f4e7c6b23aa098b4549dcba Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 4 Nov 2024 23:55:14 +0400 Subject: [PATCH 02/40] 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<>(); -- 2.47.2 From 0f8416cfb199205861b09ca0dc7cdb1811bc73f4 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Mon, 4 Nov 2024 23:56:25 +0400 Subject: [PATCH 03/40] * 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 -- 2.47.2 From c3343110f3f4052bd8e31048a1da8dc90e14a19b Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 6 Nov 2024 21:29:27 +0400 Subject: [PATCH 04/40] 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()); -- 2.47.2 From dc9f3498287c56ace4142c19768be6bc3bedece1 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 9 Nov 2024 17:55:07 +0400 Subject: [PATCH 05/40] 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); + } + } } -- 2.47.2 From 0689c56597298a511bfdcf6957b16cdb353d2816 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 9 Nov 2024 19:10:00 +0400 Subject: [PATCH 06/40] 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); + } } -- 2.47.2 From fdbc5d6409171189c858f0581a8f9733abadf7b4 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 10 Nov 2024 15:51:52 +0400 Subject: [PATCH 07/40] 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; -- 2.47.2 From 52ebba4cd125960f1cfb805a7d3fb7496a83a663 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 12 Nov 2024 00:18:08 +0400 Subject: [PATCH 08/40] 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()); } } -- 2.47.2 From 740a9347ae5628edada9f982a05679d52468a761 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 16 Nov 2024 22:12:14 +0400 Subject: [PATCH 09/40] 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() -- 2.47.2 From 6d55e4b9e62b553ead3b14498ba3d9b04f0b8b80 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 23 Nov 2024 09:15:09 +0400 Subject: [PATCH 10/40] 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)); } } -- 2.47.2 From 57ef74da9001e6b76848617f2888adf857111877 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 30 Nov 2024 03:19:24 +0400 Subject: [PATCH 11/40] 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"); -- 2.47.2 From d49ff89a81fa11d83c2a797c3a687f18e868804a Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 30 Nov 2024 03:26:03 +0400 Subject: [PATCH 12/40] 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."); } -- 2.47.2 From b1024d23fca7ac5a97348065b6cd50dc2b691573 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 30 Nov 2024 16:56:00 +0400 Subject: [PATCH 13/40] 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) { -- 2.47.2 From ecb5dccfae9f3a310db786d7e8b085a9f1162786 Mon Sep 17 00:00:00 2001 From: Cameron Merkel <44722506+Cguy7777@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:45:57 -0600 Subject: [PATCH 14/40] [DSK] Implement Ghost Vacuum (#13072) --- Mage.Sets/src/mage/cards/g/GhostVacuum.java | 114 ++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 2 + 2 files changed, 116 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GhostVacuum.java diff --git a/Mage.Sets/src/mage/cards/g/GhostVacuum.java b/Mage.Sets/src/mage/cards/g/GhostVacuum.java new file mode 100644 index 00000000000..b76bff02e39 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GhostVacuum.java @@ -0,0 +1,114 @@ +package mage.cards.g; + +import java.util.Objects; +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateAsSorceryActivatedAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.continuous.AddCreatureTypeAdditionEffect; +import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect; +import mage.cards.*; +import mage.constants.*; +import mage.counters.CounterType; +import mage.counters.Counters; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +/** + * @author Cguy7777 + */ +public final class GhostVacuum extends CardImpl { + + public GhostVacuum(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + // {T}: Exile target card from a graveyard. + Ability exileAbility + = new SimpleActivatedAbility(new ExileTargetEffect().setToSourceExileZone(true), new TapSourceCost()); + exileAbility.addTarget(new TargetCardInGraveyard()); + this.addAbility(exileAbility); + + // {6}, {T}, Sacrifice Ghost Vacuum: + // Put each creature card exiled with Ghost Vacuum onto the battlefield under your control with a flying counter on it. + // Each of them is a 1/1 Spirit in addition to its other types. Activate only as a sorcery. + Ability putOntoBattlefieldAbility + = new ActivateAsSorceryActivatedAbility(new GhostVacuumEffect(), new GenericManaCost(6)); + putOntoBattlefieldAbility.addCost(new TapSourceCost()); + putOntoBattlefieldAbility.addCost(new SacrificeSourceCost()); + this.addAbility(putOntoBattlefieldAbility); + } + + private GhostVacuum(final GhostVacuum card) { + super(card); + } + + @Override + public GhostVacuum copy() { + return new GhostVacuum(this); + } +} + +class GhostVacuumEffect extends OneShotEffect { + + GhostVacuumEffect() { + super(Outcome.Benefit); + staticText = "Put each creature card exiled with {this} onto the battlefield under your control " + + "with a flying counter on it. Each of them is a 1/1 Spirit in addition to its other types."; + } + + private GhostVacuumEffect(final GhostVacuumEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + + if (player == null || exileZone == null || exileZone.isEmpty()) { + return false; + } + + // Put each creature card exiled with Ghost Vacuum + // onto the battlefield under your control with a flying counter on it. + Cards creatureCards = new CardsImpl(exileZone.getCards(StaticFilters.FILTER_CARD_CREATURE, game)); + Counters countersToAdd = new Counters(); + countersToAdd.addCounter(CounterType.FLYING.createInstance()); + for (Card card : creatureCards.getCards(game)) { + game.setEnterWithCounters(card.getId(), countersToAdd.copy()); + } + player.moveCards(creatureCards, Zone.BATTLEFIELD, source, game); + + creatureCards.stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .forEach(permanent -> { + // Each of them is a 1/1 Spirit in addition to its other types. + ContinuousEffect effect = new SetBasePowerToughnessTargetEffect(1, 1, Duration.EndOfGame); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + + effect = new AddCreatureTypeAdditionEffect(SubType.SPIRIT, false); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + }); + return true; + } + + @Override + public GhostVacuumEffect copy() { + return new GhostVacuumEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 81fe839e138..84e21a7a17b 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -103,6 +103,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Friendly Ghost", 12, Rarity.COMMON, mage.cards.f.FriendlyGhost.class)); cards.add(new SetCardInfo("Friendly Teddy", 247, Rarity.COMMON, mage.cards.f.FriendlyTeddy.class)); cards.add(new SetCardInfo("Get Out", 60, Rarity.UNCOMMON, mage.cards.g.GetOut.class)); + cards.add(new SetCardInfo("Ghost Vacuum", 248, Rarity.RARE, mage.cards.g.GhostVacuum.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Ghost Vacuum", 326, Rarity.RARE, mage.cards.g.GhostVacuum.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Give In to Violence", 101, Rarity.COMMON, mage.cards.g.GiveInToViolence.class)); cards.add(new SetCardInfo("Glimmer Seeker", 14, Rarity.UNCOMMON, mage.cards.g.GlimmerSeeker.class)); cards.add(new SetCardInfo("Glimmerburst", 62, Rarity.COMMON, mage.cards.g.Glimmerburst.class)); -- 2.47.2 From aaa611679f3d628b3dc9734423d0412c49d8cee5 Mon Sep 17 00:00:00 2001 From: Cameron Merkel <44722506+Cguy7777@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:46:06 -0600 Subject: [PATCH 15/40] [DSK] Implement Fear of Abduction (#13079) --- .../src/mage/cards/f/FearOfAbduction.java | 125 ++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 126 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FearOfAbduction.java diff --git a/Mage.Sets/src/mage/cards/f/FearOfAbduction.java b/Mage.Sets/src/mage/cards/f/FearOfAbduction.java new file mode 100644 index 00000000000..72792029d78 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FearOfAbduction.java @@ -0,0 +1,125 @@ +package mage.cards.f; + +import java.util.UUID; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.costs.common.ExileTargetCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.util.CardUtil; + +/** + * @author Cguy7777 + */ +public final class FearOfAbduction extends CardImpl { + + public FearOfAbduction(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{W}{W}"); + + this.subtype.add(SubType.NIGHTMARE); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // As an additional cost to cast this spell, exile a creature you control. + this.getSpellAbility().addCost( + new ExileTargetCost(new TargetControlledPermanent(StaticFilters.FILTER_CONTROLLED_A_CREATURE))); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Fear of Abduction enters, exile target creature an opponent controls. + Ability ability = new EntersBattlefieldTriggeredAbility(new FearOfAbductionExileEffect()); + ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); + this.addAbility(ability); + + // When Fear of Abduction leaves the battlefield, put each card exiled with it into its owner's hand. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new FearOfAbductionReturnEffect())); + } + + private FearOfAbduction(final FearOfAbduction card) { + super(card); + } + + @Override + public FearOfAbduction copy() { + return new FearOfAbduction(this); + } +} + +class FearOfAbductionExileEffect extends OneShotEffect { + + FearOfAbductionExileEffect() { + super(Outcome.Benefit); + this.staticText = "exile target creature an opponent controls"; + } + + private FearOfAbductionExileEffect(final FearOfAbductionExileEffect effect) { + super(effect); + } + + @Override + public FearOfAbductionExileEffect copy() { + return new FearOfAbductionExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceObject = source.getSourceObject(game); + if (sourceObject != null) { + return new ExileTargetEffect( + // Offset -1 so that opponent's creature is exiled to the same exile zone as your creature + CardUtil.getExileZoneId(game, source, -1), + CardUtil.getSourceName(game, source)) + .apply(game, source); + } + return false; + } +} + +class FearOfAbductionReturnEffect extends OneShotEffect { + + FearOfAbductionReturnEffect() { + super(Outcome.Neutral); + this.staticText = "put each card exiled with it into its owner's hand"; + } + + private FearOfAbductionReturnEffect(final FearOfAbductionReturnEffect effect) { + super(effect); + } + + @Override + public FearOfAbductionReturnEffect copy() { + return new FearOfAbductionReturnEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + // Offset -2 since Fear of Abduction has left the battlefield since last time + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source, -2)); + if (exileZone != null) { + controller.moveCards(exileZone, Zone.HAND, source, game); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 84e21a7a17b..363a6bcc017 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -80,6 +80,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Ethereal Armor", 7, Rarity.UNCOMMON, mage.cards.e.EtherealArmor.class)); cards.add(new SetCardInfo("Exorcise", 8, Rarity.UNCOMMON, mage.cards.e.Exorcise.class)); cards.add(new SetCardInfo("Fanatic of the Harrowing", 96, Rarity.COMMON, mage.cards.f.FanaticOfTheHarrowing.class)); + cards.add(new SetCardInfo("Fear of Abduction", 9, Rarity.UNCOMMON, mage.cards.f.FearOfAbduction.class)); cards.add(new SetCardInfo("Fear of Being Hunted", 134, Rarity.UNCOMMON, mage.cards.f.FearOfBeingHunted.class)); cards.add(new SetCardInfo("Fear of Burning Alive", 135, Rarity.UNCOMMON, mage.cards.f.FearOfBurningAlive.class)); cards.add(new SetCardInfo("Fear of Exposure", 177, Rarity.UNCOMMON, mage.cards.f.FearOfExposure.class)); -- 2.47.2 From 1efa094564568910bd50aab39bd6475e5926da40 Mon Sep 17 00:00:00 2001 From: Cameron Merkel <44722506+Cguy7777@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:46:22 -0600 Subject: [PATCH 16/40] [DSK] Implement Omnivorous Flytrap (#13083) * [DSK] Implement Omnivorous Flytrap * Require at least one target * Use single line for rules text * Don't set minimum targets here --- .../src/mage/cards/o/OmnivorousFlytrap.java | 110 ++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 2 + 2 files changed, 112 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/o/OmnivorousFlytrap.java diff --git a/Mage.Sets/src/mage/cards/o/OmnivorousFlytrap.java b/Mage.Sets/src/mage/cards/o/OmnivorousFlytrap.java new file mode 100644 index 00000000000..7a5c2f998f8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/o/OmnivorousFlytrap.java @@ -0,0 +1,110 @@ +package mage.cards.o; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.condition.IntCompareCondition; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.DistributeCountersEffect; +import mage.abilities.hint.common.CardTypesInGraveyardHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanentAmount; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public final class OmnivorousFlytrap extends CardImpl { + + public OmnivorousFlytrap(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.PLANT); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Delirium -- Whenever Omnivorous Flytrap enters or attacks, if there are four or more card types among cards in your graveyard, distribute two +1/+1 counters among one or two target creatures. Then if there are six or more card types among cards in your graveyard, double the number of +1/+1 counters on those creatures. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility( + new DistributeCountersEffect(CounterType.P1P1, 2, "one or two target creatures")) + .withInterveningIf(DeliriumCondition.instance); + ability.addEffect(new ConditionalOneShotEffect( + new OmnivorousFlytrapEffect(), + new OmnivorousFlytrapCondition()) + .concatBy("Then")); + ability.addTarget(new TargetCreaturePermanentAmount(2)); + ability.addHint(CardTypesInGraveyardHint.YOU); + this.addAbility(ability.setAbilityWord(AbilityWord.DELIRIUM)); + } + + private OmnivorousFlytrap(final OmnivorousFlytrap card) { + super(card); + } + + @Override + public OmnivorousFlytrap copy() { + return new OmnivorousFlytrap(this); + } +} + +class OmnivorousFlytrapEffect extends OneShotEffect { + + OmnivorousFlytrapEffect() { + super(Outcome.Benefit); + staticText = "double the number of +1/+1 counters on those creatures"; + } + + private OmnivorousFlytrapEffect(final OmnivorousFlytrapEffect effect) { + super(effect); + } + + @Override + public OmnivorousFlytrapEffect copy() { + return new OmnivorousFlytrapEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + Permanent permanent = game.getPermanent(targetId); + if (permanent != null) { + int existingCounters = permanent.getCounters(game).getCount(CounterType.P1P1); + if (existingCounters > 0) { + permanent.addCounters(CounterType.P1P1.createInstance(existingCounters), source, game); + } + } + } + return true; + } +} + +class OmnivorousFlytrapCondition extends IntCompareCondition { + + OmnivorousFlytrapCondition() { + super(ComparisonType.OR_GREATER, 6); + } + + @Override + protected int getInputValue(Game game, Ability source) { + return CardTypesInGraveyardCount.YOU.calculate(game, source, null); + } + + @Override + public String toString() { + return "if there are six or more card types among cards in your graveyard"; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 363a6bcc017..8bd7ec7ddfb 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -159,6 +159,8 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Niko, Light of Hope", 224, Rarity.MYTHIC, mage.cards.n.NikoLightOfHope.class)); cards.add(new SetCardInfo("Norin, Swift Survivalist", 145, Rarity.UNCOMMON, mage.cards.n.NorinSwiftSurvivalist.class)); cards.add(new SetCardInfo("Oblivious Bookworm", 225, Rarity.UNCOMMON, mage.cards.o.ObliviousBookworm.class)); + cards.add(new SetCardInfo("Omnivorous Flytrap", 192, Rarity.RARE, mage.cards.o.OmnivorousFlytrap.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Omnivorous Flytrap", 322, Rarity.RARE, mage.cards.o.OmnivorousFlytrap.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Optimistic Scavenger", 21, Rarity.UNCOMMON, mage.cards.o.OptimisticScavenger.class)); cards.add(new SetCardInfo("Orphans of the Wheat", 22, Rarity.UNCOMMON, mage.cards.o.OrphansOfTheWheat.class)); cards.add(new SetCardInfo("Overlord of the Balemurk", 113, Rarity.MYTHIC, mage.cards.o.OverlordOfTheBalemurk.class)); -- 2.47.2 From 0a25dbe6e8dda47964ecb343bdafca0db125c3e1 Mon Sep 17 00:00:00 2001 From: Cameron Merkel <44722506+Cguy7777@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:46:32 -0600 Subject: [PATCH 17/40] [DSK] Implement The Rollercrusher Ride (#13084) * [DSK] Implement The Rollercrusher Ride * Use overflowMultiply() --- .../mage/cards/t/TheRollercrusherRide.java | 93 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 3 + 2 files changed, 96 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TheRollercrusherRide.java diff --git a/Mage.Sets/src/mage/cards/t/TheRollercrusherRide.java b/Mage.Sets/src/mage/cards/t/TheRollercrusherRide.java new file mode 100644 index 00000000000..1dbe303bd32 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheRollercrusherRide.java @@ -0,0 +1,93 @@ +package mage.cards.t; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.common.CardTypesInGraveyardHint; +import mage.constants.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.game.Game; +import mage.game.events.DamageEvent; +import mage.game.events.GameEvent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.TargetsCountAdjuster; +import mage.util.CardUtil; + +/** + * @author Cguy7777 + */ +public final class TheRollercrusherRide extends CardImpl { + + public TheRollercrusherRide(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{X}{2}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + + // Delirium -- If a source you control would deal noncombat damage to a permanent or player while there are four or more card types among cards in your graveyard, + // it deals double that damage instead. + this.addAbility(new SimpleStaticAbility(new TheRollercrusherRideEffect()) + .setAbilityWord(AbilityWord.DELIRIUM) + .addHint(CardTypesInGraveyardHint.YOU)); + + // When The Rollercrusher Ride enters, it deals X damage to each of up to X target creatures. + Ability ability = new EntersBattlefieldTriggeredAbility(new DamageTargetEffect(GetXValue.instance) + .setText("it deals X damage to each of up to X target creatures")); + ability.addTarget(new TargetCreaturePermanent(0, 0)); + ability.setTargetAdjuster(new TargetsCountAdjuster(GetXValue.instance)); + this.addAbility(ability); + } + + private TheRollercrusherRide(final TheRollercrusherRide card) { + super(card); + } + + @Override + public TheRollercrusherRide copy() { + return new TheRollercrusherRide(this); + } +} + +class TheRollercrusherRideEffect extends ReplacementEffectImpl { + + TheRollercrusherRideEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "if a source you control would deal noncombat damage to a permanent or player " + + "while there are four or more card types among cards in your graveyard, " + + "it deals double that damage instead"; + } + + private TheRollercrusherRideEffect(final TheRollercrusherRideEffect effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGE_PERMANENT + || event.getType() == GameEvent.EventType.DAMAGE_PLAYER; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.isControlledBy(game.getControllerId(event.getSourceId())) + && !((DamageEvent) event).isCombatDamage() + && DeliriumCondition.instance.apply(game, source); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + event.setAmount(CardUtil.overflowMultiply(event.getAmount(), 2)); + return false; + } + + @Override + public TheRollercrusherRideEffect copy() { + return new TheRollercrusherRideEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 8bd7ec7ddfb..54a0d029ed0 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -216,6 +216,9 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Terramorphic Expanse", 269, Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class)); cards.add(new SetCardInfo("The Jolly Balloon Man", 219, Rarity.RARE, mage.cards.t.TheJollyBalloonMan.class)); cards.add(new SetCardInfo("The Mindskinner", 66, Rarity.RARE, mage.cards.t.TheMindskinner.class)); + cards.add(new SetCardInfo("The Rollercrusher Ride", 155, Rarity.MYTHIC, mage.cards.t.TheRollercrusherRide.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Rollercrusher Ride", 298, Rarity.MYTHIC, mage.cards.t.TheRollercrusherRide.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Rollercrusher Ride", 317, Rarity.MYTHIC, mage.cards.t.TheRollercrusherRide.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Swarmweaver", 236, Rarity.RARE, mage.cards.t.TheSwarmweaver.class)); cards.add(new SetCardInfo("The Wandering Rescuer", 41, Rarity.MYTHIC, mage.cards.t.TheWanderingRescuer.class)); cards.add(new SetCardInfo("Thornspire Verge", 270, Rarity.RARE, mage.cards.t.ThornspireVerge.class)); -- 2.47.2 From d3439c8af900c4b1110af43dc3f7419912884285 Mon Sep 17 00:00:00 2001 From: Cameron Merkel <44722506+Cguy7777@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:46:42 -0600 Subject: [PATCH 18/40] [DSK] Implement Meathook Massacre II (#13085) --- .../src/mage/cards/m/MeathookMassacreII.java | 114 ++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 3 + 2 files changed, 117 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MeathookMassacreII.java diff --git a/Mage.Sets/src/mage/cards/m/MeathookMassacreII.java b/Mage.Sets/src/mage/cards/m/MeathookMassacreII.java new file mode 100644 index 00000000000..c26dab23580 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MeathookMassacreII.java @@ -0,0 +1,114 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.DiesCreatureTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DoIfCostPaid; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldWithCounterTargetEffect; +import mage.abilities.effects.common.SacrificeAllEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public final class MeathookMassacreII extends CardImpl { + + public MeathookMassacreII(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{X}{X}{B}{B}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + + // When Meathook Massacre II enters, each player sacrifices X creatures of their choice. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SacrificeAllEffect(GetXValue.instance, StaticFilters.FILTER_PERMANENT_CREATURES))); + + // Whenever a creature you control dies, you may pay 3 life. + // If you do, return that card under your control with a finality counter on it. + this.addAbility(new DiesCreatureTriggeredAbility( + new DoIfCostPaid( + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect( + CounterType.FINALITY.createInstance()) + .setText("return that card under your control with a finality counter on it"), + new PayLifeCost(3)), + false, + StaticFilters.FILTER_CONTROLLED_A_CREATURE, + true)); + + // Whenever a creature an opponent controls dies, they may pay 3 life. + // If they don't, return that card under your control with a finality counter on it. + this.addAbility(new DiesCreatureTriggeredAbility( + new MeathookMassacreIIEffect(), + false, + StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE, + true)); + } + + private MeathookMassacreII(final MeathookMassacreII card) { + super(card); + } + + @Override + public MeathookMassacreII copy() { + return new MeathookMassacreII(this); + } +} + +class MeathookMassacreIIEffect extends OneShotEffect { + + MeathookMassacreIIEffect() { + super(Outcome.PutCreatureInPlay); + staticText = "they may pay 3 life. " + + "If they don't, return that card under your control with a finality counter on it"; + } + + private MeathookMassacreIIEffect(final MeathookMassacreIIEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = getTargetPointer().getControllerOfFirstTargetOrLKI(game, source); + if (player == null) { + return false; + } + + // Whenever a creature an opponent controls dies, they may pay 3 life. + // If they don't, return that card under your control with a finality counter on it. + Cost cost = new PayLifeCost(3); + if (player.chooseUse(Outcome.Detriment, "Pay 3 life to prevent this effect?", source, game) + && cost.pay(source, game, source, player.getId(), true)) { + return true; + } + + // If a token died, card will be null and nothing will be returned to the battlefield + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null) { + return true; + } + new ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(CounterType.FINALITY.createInstance()) + .setTargetPointer(new FixedTarget(card, game)) + .apply(game, source); + return true; + } + + @Override + public MeathookMassacreIIEffect copy() { + return new MeathookMassacreIIEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 54a0d029ed0..0cb39c40099 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -148,6 +148,9 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Manifest Dread", 189, Rarity.COMMON, mage.cards.m.ManifestDread.class)); cards.add(new SetCardInfo("Marina Vendrell's Grimoire", 64, Rarity.RARE, mage.cards.m.MarinaVendrellsGrimoire.class)); cards.add(new SetCardInfo("Marvin, Murderous Mimic", 253, Rarity.RARE, mage.cards.m.MarvinMurderousMimic.class)); + cards.add(new SetCardInfo("Meathook Massacre II", 108, Rarity.MYTHIC, mage.cards.m.MeathookMassacreII.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Meathook Massacre II", 293, Rarity.MYTHIC, mage.cards.m.MeathookMassacreII.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Meathook Massacre II", 311, Rarity.MYTHIC, mage.cards.m.MeathookMassacreII.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Miasma Demon", 109, Rarity.UNCOMMON, mage.cards.m.MiasmaDemon.class)); cards.add(new SetCardInfo("Midnight Mayhem", 222, Rarity.UNCOMMON, mage.cards.m.MidnightMayhem.class)); cards.add(new SetCardInfo("Most Valuable Slayer", 144, Rarity.COMMON, mage.cards.m.MostValuableSlayer.class)); -- 2.47.2 From b1f914bbf490fcb963b13859300f0d8d1d1cd23f Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 30 Nov 2024 22:42:47 +0400 Subject: [PATCH 19/40] version bump --- Mage.Client/pom.xml | 2 +- Mage.Common/pom.xml | 2 +- Mage.Common/src/main/java/mage/utils/MageVersion.java | 4 ++-- Mage.Plugins/Mage.Counter.Plugin/pom.xml | 2 +- Mage.Plugins/pom.xml | 2 +- Mage.Reports/pom.xml | 2 +- Mage.Server.Console/pom.xml | 2 +- Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml | 2 +- Mage.Server.Plugins/Mage.Deck.Limited/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml | 2 +- .../Mage.Game.CustomPillarOfTheParunsDuel/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml | 2 +- .../Mage.Game.FreeformCommanderFreeForAll/pom.xml | 2 +- .../Mage.Game.FreeformUnlimitedCommander/pom.xml | 4 ++-- Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml | 4 ++-- Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml | 2 +- .../Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml | 2 +- Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml | 2 +- Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml | 2 +- Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml | 2 +- Mage.Server.Plugins/Mage.Player.AI/pom.xml | 2 +- Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml | 2 +- Mage.Server.Plugins/Mage.Player.Human/pom.xml | 2 +- Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml | 2 +- Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml | 2 +- Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml | 2 +- Mage.Server.Plugins/pom.xml | 2 +- Mage.Server/pom.xml | 2 +- Mage.Sets/pom.xml | 2 +- Mage.Tests/pom.xml | 2 +- Mage.Verify/pom.xml | 2 +- Mage/pom.xml | 2 +- pom.xml | 4 ++-- 41 files changed, 45 insertions(+), 45 deletions(-) diff --git a/Mage.Client/pom.xml b/Mage.Client/pom.xml index b01196abd89..cc198add2a8 100644 --- a/Mage.Client/pom.xml +++ b/Mage.Client/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-client diff --git a/Mage.Common/pom.xml b/Mage.Common/pom.xml index eccddd0df10..e9248c50e18 100644 --- a/Mage.Common/pom.xml +++ b/Mage.Common/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-common diff --git a/Mage.Common/src/main/java/mage/utils/MageVersion.java b/Mage.Common/src/main/java/mage/utils/MageVersion.java index 8402770ab52..0f7197155c1 100644 --- a/Mage.Common/src/main/java/mage/utils/MageVersion.java +++ b/Mage.Common/src/main/java/mage/utils/MageVersion.java @@ -17,8 +17,8 @@ public class MageVersion implements Serializable, Comparable { // * launcher gives priority to 1.4.48 instead 1.4.48-any-text, so don't use empty release info public static final int MAGE_VERSION_MAJOR = 1; public static final int MAGE_VERSION_MINOR = 4; - public static final int MAGE_VERSION_RELEASE = 54; - public static final String MAGE_VERSION_RELEASE_INFO = "V3"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas + public static final int MAGE_VERSION_RELEASE = 55; + public static final String MAGE_VERSION_RELEASE_INFO = "V1"; // V1, V1a, V1b for releases; V1-beta3, V1-beta4 for betas // strict mode // Each update requires a strict version diff --git a/Mage.Plugins/Mage.Counter.Plugin/pom.xml b/Mage.Plugins/Mage.Counter.Plugin/pom.xml index a53c82f0ee7..0ebcfdd8bc3 100644 --- a/Mage.Plugins/Mage.Counter.Plugin/pom.xml +++ b/Mage.Plugins/Mage.Counter.Plugin/pom.xml @@ -7,7 +7,7 @@ org.mage mage-plugins - 1.4.54 + 1.4.55 mage-counter-plugin diff --git a/Mage.Plugins/pom.xml b/Mage.Plugins/pom.xml index 2efdd9c8dcb..0e2b4f6138f 100644 --- a/Mage.Plugins/pom.xml +++ b/Mage.Plugins/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-plugins diff --git a/Mage.Reports/pom.xml b/Mage.Reports/pom.xml index d939f1f985a..f251298eced 100644 --- a/Mage.Reports/pom.xml +++ b/Mage.Reports/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-reports diff --git a/Mage.Server.Console/pom.xml b/Mage.Server.Console/pom.xml index df940eb51db..f7407a80156 100644 --- a/Mage.Server.Console/pom.xml +++ b/Mage.Server.Console/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-server-console diff --git a/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml b/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml index c23c4b4b692..6b0ba470c9e 100644 --- a/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml +++ b/Mage.Server.Plugins/Mage.Deck.Constructed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-deck-constructed diff --git a/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml b/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml index 89bc3e956d7..6093f196f59 100644 --- a/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml +++ b/Mage.Server.Plugins/Mage.Deck.Limited/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-deck-limited diff --git a/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml index eeb6b522630..de808e4f9c2 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.BrawlDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-brawlduel diff --git a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml index a1b009c1c2a..23594520bd6 100644 --- a/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.BrawlFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-brawlfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml index 8212fec501b..eb7288a4ef4 100644 --- a/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CanadianHighlanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-canadianhighlanderduel diff --git a/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml index f10d29fec70..7d6919218ed 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CommanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-commanderduel diff --git a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml index 2aeba5eb94d..5fd2deec0af 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-commanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml index fc4d8742002..190112dc919 100644 --- a/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.CustomPillarOfTheParunsDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-custompillaroftheparunsduel diff --git a/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml index d3f37d4524e..de8f60e544e 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeForAll/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-freeforall diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml index 9625ef7b338..a9dec0c425e 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-freeformcommanderduel diff --git a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml index ab43f71efbe..0045b6ea026 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeformCommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-freeformcommanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml b/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml index c533ce926dc..9d091c25637 100644 --- a/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.FreeformUnlimitedCommander/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-freeformunlimitedcommander @@ -23,7 +23,7 @@ org.mage mage-game-freeformcommanderfreeforall - 1.4.54 + 1.4.55 compile diff --git a/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml index eeb2c4a8b0d..23aaef8f534 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.MomirDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-momirduel diff --git a/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml b/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml index fcdf035555b..4e403e3f4cd 100644 --- a/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.MomirGame/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-momirfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml index 0948973c6cc..8a79934e501 100644 --- a/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.OathbreakerDuel/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-oathbreakerduel @@ -22,7 +22,7 @@ org.mage mage-game-oathbreakerfreeforall - 1.4.54 + 1.4.55 compile diff --git a/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml index 9e35b646af0..6a906431afd 100644 --- a/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-oathbreakerfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml index fa7df6db7ee..bc221cf10e3 100644 --- a/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/pom.xml @@ -6,7 +6,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-pennydreadfulcommanderfreeforall diff --git a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml index 53a85916626..ef4ea9bea06 100644 --- a/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-tinyleadersduel diff --git a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml index 876be9e6ac2..35664806139 100644 --- a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml +++ b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-game-twoplayerduel diff --git a/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml b/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml index 29e7e6e7bd0..8afec9cdd1c 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI.DraftBot/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-player-ai-draftbot diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml b/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml index d18b3d20610..9ae6ceb0bd5 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-player-ai-ma diff --git a/Mage.Server.Plugins/Mage.Player.AI/pom.xml b/Mage.Server.Plugins/Mage.Player.AI/pom.xml index 0003de8c6fa..4be51267f30 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AI/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-player-ai diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml b/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml index d8871a0cf3c..f2fa92296ab 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-player-ai-mcts diff --git a/Mage.Server.Plugins/Mage.Player.Human/pom.xml b/Mage.Server.Plugins/Mage.Player.Human/pom.xml index 334acab1a06..de5ddc29d61 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.Human/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-player-human diff --git a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml index 1b1d7e3f554..39150f8e260 100644 --- a/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.BoosterDraft/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-tournament-boosterdraft diff --git a/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml b/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml index 1926e65f038..2760714e8d2 100644 --- a/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.Constructed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-tournament-constructed diff --git a/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml b/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml index f8c8721dc0b..9b494faa29b 100644 --- a/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml +++ b/Mage.Server.Plugins/Mage.Tournament.Sealed/pom.xml @@ -7,7 +7,7 @@ org.mage mage-server-plugins - 1.4.54 + 1.4.55 mage-tournament-sealed diff --git a/Mage.Server.Plugins/pom.xml b/Mage.Server.Plugins/pom.xml index b1a63a49142..5ba184819b3 100644 --- a/Mage.Server.Plugins/pom.xml +++ b/Mage.Server.Plugins/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-server-plugins diff --git a/Mage.Server/pom.xml b/Mage.Server/pom.xml index 76fc2f09373..9ee564862c7 100644 --- a/Mage.Server/pom.xml +++ b/Mage.Server/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-server diff --git a/Mage.Sets/pom.xml b/Mage.Sets/pom.xml index 6afd2a5cdb7..a33ae70434b 100644 --- a/Mage.Sets/pom.xml +++ b/Mage.Sets/pom.xml @@ -7,7 +7,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-sets diff --git a/Mage.Tests/pom.xml b/Mage.Tests/pom.xml index 86e99e326e8..76334a3fa9e 100644 --- a/Mage.Tests/pom.xml +++ b/Mage.Tests/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-tests diff --git a/Mage.Verify/pom.xml b/Mage.Verify/pom.xml index 03474793408..ac62b70e9db 100644 --- a/Mage.Verify/pom.xml +++ b/Mage.Verify/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage-verify diff --git a/Mage/pom.xml b/Mage/pom.xml index a1d7ab2f646..e5c1184ee14 100644 --- a/Mage/pom.xml +++ b/Mage/pom.xml @@ -6,7 +6,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 mage diff --git a/pom.xml b/pom.xml index ae0996dbc27..34f0f0a8109 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.mage mage-root - 1.4.54 + 1.4.55 pom Mage Root Mage Root POM @@ -16,7 +16,7 @@ ${project.basedir} - 1.4.54 + 1.4.55 -Dfile.encoding=UTF-8 UTF-8 yyyy-MM-dd'T'HH:mm:ss'Z' -- 2.47.2 From faeca638de70d80ea46db2d9453910d2ba3dc0d3 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 29 Nov 2024 02:34:43 -0500 Subject: [PATCH 20/40] refactor: clean up some superfluous null filters in optionals --- Mage.Sets/src/mage/cards/a/AirtightAlibi.java | 1 - Mage.Sets/src/mage/cards/a/AssaultIntercessor.java | 2 -- Mage.Sets/src/mage/cards/b/BrassKnuckles.java | 1 - Mage.Sets/src/mage/cards/c/ConquerorsFlail.java | 1 - Mage.Sets/src/mage/cards/e/ElspethsTalent.java | 1 - Mage.Sets/src/mage/cards/e/EmergentWoodwurm.java | 1 - Mage.Sets/src/mage/cards/g/GuardianScalelord.java | 1 - Mage.Sets/src/mage/cards/n/NarsetEnlightenedExile.java | 3 +-- Mage.Sets/src/mage/cards/p/PainDistributor.java | 2 -- Mage.Sets/src/mage/cards/q/QuintoriusLoremaster.java | 1 - Mage.Sets/src/mage/cards/r/RangersOfIthilien.java | 1 - Mage.Sets/src/mage/cards/r/Ringwraiths.java | 2 -- Mage.Sets/src/mage/cards/s/SurgeEngine.java | 2 -- Mage.Sets/src/mage/cards/t/ThrashingFrontliner.java | 1 - Mage.Sets/src/mage/cards/w/WarTrainedSlasher.java | 1 - 15 files changed, 1 insertion(+), 20 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AirtightAlibi.java b/Mage.Sets/src/mage/cards/a/AirtightAlibi.java index b534379c2fc..a6621508eb8 100644 --- a/Mage.Sets/src/mage/cards/a/AirtightAlibi.java +++ b/Mage.Sets/src/mage/cards/a/AirtightAlibi.java @@ -125,7 +125,6 @@ class AirtightAlibiReplacementEffect extends ReplacementEffectImpl { public boolean applies(GameEvent event, Ability source, Game game) { return Optional .ofNullable(source.getSourcePermanentIfItStillExists(game)) - .filter(Objects::nonNull) .map(Permanent::getAttachedTo) .map(event.getTargetId()::equals) .orElse(false); diff --git a/Mage.Sets/src/mage/cards/a/AssaultIntercessor.java b/Mage.Sets/src/mage/cards/a/AssaultIntercessor.java index 8a12da72ce2..245cec70910 100644 --- a/Mage.Sets/src/mage/cards/a/AssaultIntercessor.java +++ b/Mage.Sets/src/mage/cards/a/AssaultIntercessor.java @@ -76,11 +76,9 @@ class AssaultIntercessorEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { return Optional .ofNullable(getValue("creatureDied")) - .filter(Objects::nonNull) .map(Permanent.class::cast) .map(Controllable::getControllerId) .map(game::getPlayer) - .filter(Objects::nonNull) .map(player -> player.loseLife(2, game, source, false) > 0) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/b/BrassKnuckles.java b/Mage.Sets/src/mage/cards/b/BrassKnuckles.java index cc5a5b226b0..830e18e7cf1 100644 --- a/Mage.Sets/src/mage/cards/b/BrassKnuckles.java +++ b/Mage.Sets/src/mage/cards/b/BrassKnuckles.java @@ -67,7 +67,6 @@ enum BrassKnucklesCondition implements Condition { .ofNullable(source.getSourcePermanentIfItStillExists(game)) .map(Permanent::getAttachedTo) .map(game::getPermanent) - .filter(Objects::nonNull) .map(Permanent::getAttachments) .map(Collection::stream) .map(stream -> stream.map(game::getPermanent)) diff --git a/Mage.Sets/src/mage/cards/c/ConquerorsFlail.java b/Mage.Sets/src/mage/cards/c/ConquerorsFlail.java index 280501b12d1..32539d56033 100644 --- a/Mage.Sets/src/mage/cards/c/ConquerorsFlail.java +++ b/Mage.Sets/src/mage/cards/c/ConquerorsFlail.java @@ -114,7 +114,6 @@ class ConquerorsFlailEffect extends ContinuousRuleModifyingEffectImpl { .ofNullable(source.getSourcePermanentIfItStillExists(game)) .map(Permanent::getAttachedTo) .map(game::getPermanent) - .filter(Objects::nonNull) .map(permanent -> permanent.isCreature(game)) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/e/ElspethsTalent.java b/Mage.Sets/src/mage/cards/e/ElspethsTalent.java index 0bf855505d6..04f1c3a2123 100644 --- a/Mage.Sets/src/mage/cards/e/ElspethsTalent.java +++ b/Mage.Sets/src/mage/cards/e/ElspethsTalent.java @@ -90,7 +90,6 @@ class ElspethsTalentTriggeredAbility extends TriggeredAbilityImpl { && event.getSourceId().equals(permanent.getAttachedTo()) && isControlledBy(event.getPlayerId()) && Optional.ofNullable(game.getStack().getStackObject(event.getSourceId())) - .filter(Objects::nonNull) .map(StackObject::getStackAbility) .map(LoyaltyAbility.class::isInstance) .orElse(false); diff --git a/Mage.Sets/src/mage/cards/e/EmergentWoodwurm.java b/Mage.Sets/src/mage/cards/e/EmergentWoodwurm.java index fb1b81d6d81..d9472c83b64 100644 --- a/Mage.Sets/src/mage/cards/e/EmergentWoodwurm.java +++ b/Mage.Sets/src/mage/cards/e/EmergentWoodwurm.java @@ -69,7 +69,6 @@ enum EmergentWoodwurmPredicate implements ObjectSourcePlayerPredicate { .of(input) .map(ObjectSourcePlayer::getSource) .map(ability -> ability.getSourcePermanentOrLKI(game)) - .filter(Objects::nonNull) .map(MageObject::getPower) .map(MageInt::getValue) .map(i -> input.getObject().getManaValue() <= i) diff --git a/Mage.Sets/src/mage/cards/g/GuardianScalelord.java b/Mage.Sets/src/mage/cards/g/GuardianScalelord.java index af29bf49010..17454291a1b 100644 --- a/Mage.Sets/src/mage/cards/g/GuardianScalelord.java +++ b/Mage.Sets/src/mage/cards/g/GuardianScalelord.java @@ -77,7 +77,6 @@ enum GuardianScalelordPredicate implements ObjectSourcePlayerPredicate { public boolean apply(ObjectSourcePlayer input, Game game) { return Optional .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) - .filter(Objects::nonNull) .map(MageObject::getPower) .map(MageInt::getValue) .map(p -> input.getObject().getManaValue() <= p) diff --git a/Mage.Sets/src/mage/cards/n/NarsetEnlightenedExile.java b/Mage.Sets/src/mage/cards/n/NarsetEnlightenedExile.java index c9f1c13b644..bee904dc0dc 100644 --- a/Mage.Sets/src/mage/cards/n/NarsetEnlightenedExile.java +++ b/Mage.Sets/src/mage/cards/n/NarsetEnlightenedExile.java @@ -78,10 +78,9 @@ enum NarsetEnlightenedExilePredicate implements ObjectSourcePlayerPredicate input, Game game) { return Optional .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) - .filter(Objects::nonNull) .map(MageObject::getPower) .map(MageInt::getValue) .map(p -> input.getObject().getManaValue() < p) .orElse(false); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/p/PainDistributor.java b/Mage.Sets/src/mage/cards/p/PainDistributor.java index cb50f2654e1..85595a0f134 100644 --- a/Mage.Sets/src/mage/cards/p/PainDistributor.java +++ b/Mage.Sets/src/mage/cards/p/PainDistributor.java @@ -103,11 +103,9 @@ class PainDistributorEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { return Optional .ofNullable(getValue("permanentDied")) - .filter(Objects::nonNull) .map(Permanent.class::cast) .map(Controllable::getControllerId) .map(game::getPlayer) - .filter(Objects::nonNull) .map(player -> player.damage(1, source, game) > 0) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/q/QuintoriusLoremaster.java b/Mage.Sets/src/mage/cards/q/QuintoriusLoremaster.java index ca664488b90..d4c3db7f3f2 100644 --- a/Mage.Sets/src/mage/cards/q/QuintoriusLoremaster.java +++ b/Mage.Sets/src/mage/cards/q/QuintoriusLoremaster.java @@ -110,7 +110,6 @@ enum QuintoriusLoremasterPredicate implements ObjectSourcePlayerPredicate UUID exileZoneId = CardUtil.getExileZoneId(game, quintorius.getId(), quintorius.getZoneChangeCounter(game)); return exile.getExileZone(exileZoneId); }) - .filter(Objects::nonNull) .map(exile -> exile.contains(input.getObject().getId())) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/r/RangersOfIthilien.java b/Mage.Sets/src/mage/cards/r/RangersOfIthilien.java index bbce9a324ff..e0ef3234804 100644 --- a/Mage.Sets/src/mage/cards/r/RangersOfIthilien.java +++ b/Mage.Sets/src/mage/cards/r/RangersOfIthilien.java @@ -70,7 +70,6 @@ enum RangersOfIthilienPredicate implements ObjectSourcePlayerPredicate input, Game game) { return Optional .ofNullable(input.getSource().getSourcePermanentIfItStillExists(game)) - .filter(Objects::nonNull) .map(MageObject::getPower) .map(MageInt::getValue) .map(i -> i > input.getObject().getPower().getValue()) diff --git a/Mage.Sets/src/mage/cards/r/Ringwraiths.java b/Mage.Sets/src/mage/cards/r/Ringwraiths.java index 0919b94bb08..10dbec9e613 100644 --- a/Mage.Sets/src/mage/cards/r/Ringwraiths.java +++ b/Mage.Sets/src/mage/cards/r/Ringwraiths.java @@ -77,11 +77,9 @@ class RingwraithsEffect extends OneShotEffect { return Optional .ofNullable(getTargetPointer().getFirst(game, source)) .map(game::getPermanent) - .filter(Objects::nonNull) .filter(permanent -> permanent.isLegendary(game)) .map(Controllable::getControllerId) .map(game::getPlayer) - .filter(Objects::nonNull) .map(player -> player.loseLife(3, game, source, false) > 0) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/s/SurgeEngine.java b/Mage.Sets/src/mage/cards/s/SurgeEngine.java index a230a0b0b65..0e525fa1cf4 100644 --- a/Mage.Sets/src/mage/cards/s/SurgeEngine.java +++ b/Mage.Sets/src/mage/cards/s/SurgeEngine.java @@ -79,7 +79,6 @@ enum SurgeEngineCondition implements Condition { public boolean apply(Game game, Ability source) { return Optional .ofNullable(source.getSourcePermanentIfItStillExists(game)) - .filter(Objects::nonNull) .map(permanent -> !permanent.hasAbility(DefenderAbility.getInstance(), game)) .orElse(false); } @@ -94,7 +93,6 @@ class SurgeEngineAbility extends ActivatedAbilityImpl { private static final Condition staticCondition = (game, source) -> Optional .ofNullable(source.getSourcePermanentIfItStillExists(game)) - .filter(Objects::nonNull) .map(permanent -> permanent.getColor(game)) .map(ObjectColor::isBlue) .orElse(false); diff --git a/Mage.Sets/src/mage/cards/t/ThrashingFrontliner.java b/Mage.Sets/src/mage/cards/t/ThrashingFrontliner.java index bdfb2ce261b..ef963b2b662 100644 --- a/Mage.Sets/src/mage/cards/t/ThrashingFrontliner.java +++ b/Mage.Sets/src/mage/cards/t/ThrashingFrontliner.java @@ -74,7 +74,6 @@ class ThrashingFrontlinerTriggeredAbility extends TriggeredAbilityImpl { .ofNullable(this.getSourceId()) .map(game.getCombat()::getDefenderId) .map(game::getPermanent) - .filter(Objects::nonNull) .map(permanent -> permanent.isBattle(game)) .orElse(false); } diff --git a/Mage.Sets/src/mage/cards/w/WarTrainedSlasher.java b/Mage.Sets/src/mage/cards/w/WarTrainedSlasher.java index 6f2e4e1aee4..b77283e273e 100644 --- a/Mage.Sets/src/mage/cards/w/WarTrainedSlasher.java +++ b/Mage.Sets/src/mage/cards/w/WarTrainedSlasher.java @@ -73,7 +73,6 @@ class WarTrainedSlasherTriggeredAbility extends TriggeredAbilityImpl { .ofNullable(this.getSourceId()) .map(game.getCombat()::getDefenderId) .map(game::getPermanent) - .filter(Objects::nonNull) .map(permanent -> permanent.isBattle(game)) .orElse(false); } -- 2.47.2 From 7292639137f2cc04e868da5f8e5314c1f5fa902e Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 29 Nov 2024 02:35:41 -0500 Subject: [PATCH 21/40] text fix --- Mage.Sets/src/mage/cards/s/SeasonOfLoss.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java b/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java index 581db0f199d..0d9a6b36393 100644 --- a/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java +++ b/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java @@ -74,7 +74,7 @@ enum SeasonOfLossValue implements DynamicValue { @Override public String getMessage() { - return "creature you controlled that died this turn"; + return "creature that died under your control this turn"; } @Override -- 2.47.2 From 729869ec368656044f6182598de485cbb8c2dad7 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 29 Nov 2024 02:36:17 -0500 Subject: [PATCH 22/40] update text: sacrifice "of their choice" --- Mage.Sets/src/mage/cards/c/ClipWings.java | 4 +- Mage.Sets/src/mage/cards/g/GravePact.java | 52 +++---------------- Mage.Sets/src/mage/cards/m/MyrkulsEdict.java | 2 +- .../mage/cards/r/RampageOfTheValkyries.java | 2 +- Mage.Sets/src/mage/cards/r/RunAfoul.java | 3 +- .../mage/cards/s/SavraQueenOfTheGolgari.java | 2 +- .../src/mage/cards/s/SporogenicInfection.java | 6 ++- .../src/mage/cards/s/StructuralCollapse.java | 5 +- Mage.Sets/src/mage/cards/v/VileMutilator.java | 2 +- .../common/DrawDiscardTargetEffect.java | 23 ++++---- .../effects/common/SacrificeAllEffect.java | 1 + .../effects/common/SacrificeEffect.java | 6 ++- .../SacrificeOpponentsUnlessPayEffect.java | 2 +- 13 files changed, 41 insertions(+), 69 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/ClipWings.java b/Mage.Sets/src/mage/cards/c/ClipWings.java index b6a3db21273..40e5ea7c351 100644 --- a/Mage.Sets/src/mage/cards/c/ClipWings.java +++ b/Mage.Sets/src/mage/cards/c/ClipWings.java @@ -1,4 +1,3 @@ - package mage.cards.c; import java.util.UUID; @@ -26,7 +25,8 @@ public final class ClipWings extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{G}"); // Each opponent sacrifices a creature with flying. - this.getSpellAbility().addEffect(new SacrificeOpponentsEffect(filter)); + this.getSpellAbility().addEffect(new SacrificeOpponentsEffect(filter) + .setText("each opponent sacrifices a creature of their choice with flying")); } private ClipWings(final ClipWings card) { diff --git a/Mage.Sets/src/mage/cards/g/GravePact.java b/Mage.Sets/src/mage/cards/g/GravePact.java index 4c5a036722d..ccfd32d1d99 100644 --- a/Mage.Sets/src/mage/cards/g/GravePact.java +++ b/Mage.Sets/src/mage/cards/g/GravePact.java @@ -1,21 +1,16 @@ package mage.cards.g; -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DiesCreatureTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetSacrifice; import java.util.ArrayList; @@ -30,8 +25,11 @@ 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()); + this.addAbility(new DiesCreatureTriggeredAbility( + new GravePactEffect(), false, StaticFilters.FILTER_CONTROLLED_A_CREATURE + )); } private GravePact(final GravePact card) { @@ -44,49 +42,11 @@ public final class GravePact extends CardImpl { } } -class GravePactTriggeredAbility extends TriggeredAbilityImpl { - - public GravePactTriggeredAbility() { - super(Zone.BATTLEFIELD, new GravePactEffect()); - setTriggerPhrase("Whenever a creature you control dies, "); - this.setLeavesTheBattlefieldTrigger(true); - } - - private GravePactTriggeredAbility(final GravePactTriggeredAbility ability) { - super(ability); - } - - @Override - public GravePactTriggeredAbility copy() { - return new GravePactTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ZONE_CHANGE; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - ZoneChangeEvent zoneChangeEvent = (ZoneChangeEvent) event; - if (zoneChangeEvent.isDiesEvent()) { - Permanent permanent = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD); - return permanent != null && permanent.isControlledBy(this.getControllerId()) && permanent.isCreature(game); - } - return false; - } - - @Override - public boolean isInUseableZone(Game game, MageObject sourceObject, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, sourceObject, event, game); - } -} - class GravePactEffect extends OneShotEffect { GravePactEffect() { super(Outcome.Sacrifice); - this.staticText = "each other player sacrifices a creature"; + this.staticText = "each other player sacrifices a creature of their choice"; } private GravePactEffect(final GravePactEffect effect) { diff --git a/Mage.Sets/src/mage/cards/m/MyrkulsEdict.java b/Mage.Sets/src/mage/cards/m/MyrkulsEdict.java index 6ed2743b0b7..7bc83dd5b7b 100644 --- a/Mage.Sets/src/mage/cards/m/MyrkulsEdict.java +++ b/Mage.Sets/src/mage/cards/m/MyrkulsEdict.java @@ -66,7 +66,7 @@ class MyrkulsEdictEffect extends OneShotEffect { MyrkulsEdictEffect() { super(Outcome.Benefit); - staticText = "choose an opponent. That player sacrifices a creature"; + staticText = "choose an opponent. That player sacrifices a creature of their choice"; } private MyrkulsEdictEffect(final MyrkulsEdictEffect effect) { diff --git a/Mage.Sets/src/mage/cards/r/RampageOfTheValkyries.java b/Mage.Sets/src/mage/cards/r/RampageOfTheValkyries.java index 3e4088341c1..c7a83bedf83 100644 --- a/Mage.Sets/src/mage/cards/r/RampageOfTheValkyries.java +++ b/Mage.Sets/src/mage/cards/r/RampageOfTheValkyries.java @@ -55,7 +55,7 @@ class RampageOfTheValkyriesEffect extends OneShotEffect { RampageOfTheValkyriesEffect() { super(Outcome.Benefit); - staticText = "each other player sacrifices a creature"; + staticText = "each other player sacrifices a creature of their choice"; } private RampageOfTheValkyriesEffect(final RampageOfTheValkyriesEffect effect) { diff --git a/Mage.Sets/src/mage/cards/r/RunAfoul.java b/Mage.Sets/src/mage/cards/r/RunAfoul.java index 00d2fb3e2a3..e2d0d55f990 100644 --- a/Mage.Sets/src/mage/cards/r/RunAfoul.java +++ b/Mage.Sets/src/mage/cards/r/RunAfoul.java @@ -26,7 +26,8 @@ public final class RunAfoul extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}"); // Target opponent sacrifices a creature with flying. - this.getSpellAbility().addEffect(new SacrificeEffect(filter, 1, "Target opponent")); + this.getSpellAbility().addEffect(new SacrificeEffect(filter, 1, "Target opponent") + .setText("target opponent sacrifices a creature of their choice with flying")); this.getSpellAbility().addTarget(new TargetOpponent()); } diff --git a/Mage.Sets/src/mage/cards/s/SavraQueenOfTheGolgari.java b/Mage.Sets/src/mage/cards/s/SavraQueenOfTheGolgari.java index 83c226df1b6..872a3a3bbaa 100644 --- a/Mage.Sets/src/mage/cards/s/SavraQueenOfTheGolgari.java +++ b/Mage.Sets/src/mage/cards/s/SavraQueenOfTheGolgari.java @@ -70,7 +70,7 @@ class SavraSacrificeEffect extends OneShotEffect { SavraSacrificeEffect() { super(Outcome.Sacrifice); - this.staticText = "each other player sacrifices a creature"; + this.staticText = "each other player sacrifices a creature of their choice"; } private SavraSacrificeEffect(final SavraSacrificeEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/SporogenicInfection.java b/Mage.Sets/src/mage/cards/s/SporogenicInfection.java index b1002ed7f2a..75c17441582 100644 --- a/Mage.Sets/src/mage/cards/s/SporogenicInfection.java +++ b/Mage.Sets/src/mage/cards/s/SporogenicInfection.java @@ -46,7 +46,8 @@ public final class SporogenicInfection extends CardImpl { this.addAbility(new EnchantAbility(auraTarget)); // When Sporogenic Infection enters, target player sacrifices a creature other than enchanted creature. - Ability ability = new EntersBattlefieldTriggeredAbility(new SacrificeEffect(filter, 1, "target player")); + Ability ability = new EntersBattlefieldTriggeredAbility(new SacrificeEffect(filter, 1, "target player") + .setText("target player sacrifices a creature of their choice other than enchanted creature")); ability.addTarget(new TargetPlayer()); this.addAbility(ability); @@ -75,6 +76,7 @@ enum SporogenicInfectionPredicate implements ObjectSourcePlayerPredicate 1 ? "s" : "") - .append(", then discards ") - .append(CardUtil.numberToText(cardsToDiscard, "a")) - .append(" card") - .append(cardsToDiscard > 1 ? "s" : "") - .append(random ? " at random" : "") - .toString(); } private DrawDiscardTargetEffect(final DrawDiscardTargetEffect effect) { @@ -59,4 +50,16 @@ public class DrawDiscardTargetEffect extends OneShotEffect { } return false; } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return getTargetPointer().describeTargets(mode.getTargets(), "that player") + " draws " + + CardUtil.numberToText(cardsToDraw, "a") + " card" + (cardsToDraw > 1 ? "s" : "") + + ", then discards " + CardUtil.numberToText(cardsToDiscard, "a") + + " card" + (cardsToDiscard > 1 ? "s" : "") + (random ? " at random" : ""); + } + } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java index 6b637d76dbd..d6a300d9fa1 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java @@ -139,6 +139,7 @@ public class SacrificeAllEffect extends OneShotEffect { sb.append(' '); sb.append(filter.getMessage()); } + sb.append(" of their choice"); staticText = sb.toString(); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java index 32577d03b00..034cff1cd46 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeEffect.java @@ -91,7 +91,8 @@ public class SacrificeEffect extends OneShotEffect { if (preText != null) { sb.append(preText); } - if (preText != null && (preText.endsWith("player") || preText.endsWith("opponent") || preText.endsWith("controller"))) { + boolean playerSacs = preText != null && (preText.endsWith("player") || preText.endsWith("opponent") || preText.endsWith("controller")); + if (playerSacs) { sb.append(" sacrifices "); } else { if (preText == null || preText.isEmpty()) { @@ -107,6 +108,9 @@ public class SacrificeEffect extends OneShotEffect { sb.append(" "); sb.append(filter.getMessage()); } + if (playerSacs && !filter.getMessage().contains("with")) { + sb.append(" of their choice"); + } staticText = sb.toString(); } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java index 841164a7b29..63bca73a3b5 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeOpponentsUnlessPayEffect.java @@ -34,7 +34,7 @@ public class SacrificeOpponentsUnlessPayEffect extends OneShotEffect { super(Outcome.Sacrifice); this.cost = cost; this.filter = filter; - this.staticText = "each opponent sacrifices " + CardUtil.addArticle(filter.getMessage()) + " unless they " + CardUtil.addCostVerb(cost.getText()); + this.staticText = "each opponent sacrifices " + CardUtil.addArticle(filter.getMessage()) + " of their choice unless they " + CardUtil.addCostVerb(cost.getText()); } protected SacrificeOpponentsUnlessPayEffect(final SacrificeOpponentsUnlessPayEffect effect) { -- 2.47.2 From 8ef7d7816efe5743c927cdb68835783fe9d259ca Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 29 Nov 2024 02:37:36 -0500 Subject: [PATCH 23/40] text update: during your turn --- Mage.Sets/src/mage/cards/a/AnaraWolvidFamiliar.java | 2 +- Mage.Sets/src/mage/cards/a/AtKnifepoint.java | 2 +- Mage.Sets/src/mage/cards/b/BayekOfSiwa.java | 2 +- Mage.Sets/src/mage/cards/b/BilbosRing.java | 2 +- Mage.Sets/src/mage/cards/c/CircleOfTheMoonDruid.java | 2 +- Mage.Sets/src/mage/cards/d/DapperShieldmate.java | 2 +- Mage.Sets/src/mage/cards/d/Discontinuity.java | 2 +- Mage.Sets/src/mage/cards/d/DuelistOfDeepFaith.java | 2 +- Mage.Sets/src/mage/cards/f/FaithfulPikemaster.java | 2 +- Mage.Sets/src/mage/cards/f/ForgeAnew.java | 4 ++-- Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java | 2 +- Mage.Sets/src/mage/cards/g/GuardianNaga.java | 2 +- Mage.Sets/src/mage/cards/h/HexgoldHalberd.java | 2 +- Mage.Sets/src/mage/cards/h/Hookblade.java | 2 +- Mage.Sets/src/mage/cards/h/HookbladeVeteran.java | 2 +- Mage.Sets/src/mage/cards/j/JavelinOfLightning.java | 2 +- Mage.Sets/src/mage/cards/k/Knife.java | 2 +- Mage.Sets/src/mage/cards/l/LeechFanatic.java | 2 +- Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java | 2 +- Mage.Sets/src/mage/cards/m/MiteOverseer.java | 2 +- Mage.Sets/src/mage/cards/p/PravaOfTheSteelLegion.java | 2 +- Mage.Sets/src/mage/cards/s/ShaoJun.java | 2 +- Mage.Sets/src/mage/cards/s/SkophosReaver.java | 2 +- Mage.Sets/src/mage/cards/s/SporebackWolf.java | 2 +- Mage.Sets/src/mage/cards/z/ZamrielSeraphOfSteel.java | 2 +- 25 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AnaraWolvidFamiliar.java b/Mage.Sets/src/mage/cards/a/AnaraWolvidFamiliar.java index 8a079e6b1db..70858cee6cb 100644 --- a/Mage.Sets/src/mage/cards/a/AnaraWolvidFamiliar.java +++ b/Mage.Sets/src/mage/cards/a/AnaraWolvidFamiliar.java @@ -44,7 +44,7 @@ public final class AnaraWolvidFamiliar extends CardImpl { new GainAbilityControlledEffect( IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, filter - ), MyTurnCondition.instance, "as long as it's your turn, " + + ), MyTurnCondition.instance, "during your turn, " + "commanders you control have indestructible" ))); diff --git a/Mage.Sets/src/mage/cards/a/AtKnifepoint.java b/Mage.Sets/src/mage/cards/a/AtKnifepoint.java index df7ab794ce0..8b0055caf08 100644 --- a/Mage.Sets/src/mage/cards/a/AtKnifepoint.java +++ b/Mage.Sets/src/mage/cards/a/AtKnifepoint.java @@ -34,7 +34,7 @@ public final class AtKnifepoint extends CardImpl { // As long as it's your turn, outlaws you control have first strike. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter), - MyTurnCondition.instance, "as long as it's your turn, outlaws you control have first strike" + MyTurnCondition.instance, "during your turn, outlaws you control have first strike" ))); // Whenever you commit a crime, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery." This ability triggers only once each turn. diff --git a/Mage.Sets/src/mage/cards/b/BayekOfSiwa.java b/Mage.Sets/src/mage/cards/b/BayekOfSiwa.java index 62b45254497..563dd830ce6 100644 --- a/Mage.Sets/src/mage/cards/b/BayekOfSiwa.java +++ b/Mage.Sets/src/mage/cards/b/BayekOfSiwa.java @@ -47,7 +47,7 @@ public final class BayekOfSiwa extends CardImpl { this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityControlledEffect( DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter, true - ), MyTurnCondition.instance, "as long as it's your turn, " + + ), MyTurnCondition.instance, "during your turn, " + "other historic creatures you control have double strike" ))); diff --git a/Mage.Sets/src/mage/cards/b/BilbosRing.java b/Mage.Sets/src/mage/cards/b/BilbosRing.java index 98cfcdf00c9..eba03249b05 100644 --- a/Mage.Sets/src/mage/cards/b/BilbosRing.java +++ b/Mage.Sets/src/mage/cards/b/BilbosRing.java @@ -39,7 +39,7 @@ public final class BilbosRing extends CardImpl { // As long as it's your turn, equipped creature has hexproof and can't be blocked. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityAttachedEffect(HexproofAbility.getInstance(), AttachmentType.EQUIPMENT), - MyTurnCondition.instance, "as long as it's your turn, equipped creature has hexproof" + MyTurnCondition.instance, "during your turn, equipped creature has hexproof" )); ability.addEffect(new ConditionalRestrictionEffect( new CantBeBlockedAttachedEffect(AttachmentType.EQUIPMENT), diff --git a/Mage.Sets/src/mage/cards/c/CircleOfTheMoonDruid.java b/Mage.Sets/src/mage/cards/c/CircleOfTheMoonDruid.java index 7b2792ab071..11b3632e532 100644 --- a/Mage.Sets/src/mage/cards/c/CircleOfTheMoonDruid.java +++ b/Mage.Sets/src/mage/cards/c/CircleOfTheMoonDruid.java @@ -44,7 +44,7 @@ class CircleOfTheMoonDruidBearEffect extends ContinuousEffectImpl { CircleOfTheMoonDruidBearEffect() { super(Duration.WhileOnBattlefield, Outcome.Benefit); - staticText = "as long as it's your turn, {this} is a Bear with base power and toughness 4/2"; + staticText = "during your turn, {this} is a Bear with base power and toughness 4/2"; } private CircleOfTheMoonDruidBearEffect(final CircleOfTheMoonDruidBearEffect effect) { diff --git a/Mage.Sets/src/mage/cards/d/DapperShieldmate.java b/Mage.Sets/src/mage/cards/d/DapperShieldmate.java index 259a8365775..399434b3768 100644 --- a/Mage.Sets/src/mage/cards/d/DapperShieldmate.java +++ b/Mage.Sets/src/mage/cards/d/DapperShieldmate.java @@ -39,7 +39,7 @@ public final class DapperShieldmate extends CardImpl { // As long as it's your turn, Dapper Shieldmate gets +2/+0. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostSourceEffect(2, 0, Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} gets +2/+0" + MyTurnCondition.instance, "during your turn, {this} gets +2/+0" ))); } diff --git a/Mage.Sets/src/mage/cards/d/Discontinuity.java b/Mage.Sets/src/mage/cards/d/Discontinuity.java index 23fcadd095b..1df0bfe7fad 100644 --- a/Mage.Sets/src/mage/cards/d/Discontinuity.java +++ b/Mage.Sets/src/mage/cards/d/Discontinuity.java @@ -24,7 +24,7 @@ public final class Discontinuity extends CardImpl { // As long as it's your turn, this spell costs {2}{U}{U} less to cast. this.addAbility(new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceEffect( new ManaCostsImpl<>("{2}{U}{U}"), MyTurnCondition.instance - ).setText("as long as it's your turn, this spell costs {2}{U}{U} less to cast")) + ).setText("during your turn, this spell costs {2}{U}{U} less to cast")) .setRuleAtTheTop(true) .addHint(MyTurnHint.instance)); diff --git a/Mage.Sets/src/mage/cards/d/DuelistOfDeepFaith.java b/Mage.Sets/src/mage/cards/d/DuelistOfDeepFaith.java index 7bf47090cad..37f43cfc5ac 100644 --- a/Mage.Sets/src/mage/cards/d/DuelistOfDeepFaith.java +++ b/Mage.Sets/src/mage/cards/d/DuelistOfDeepFaith.java @@ -34,7 +34,7 @@ public final class DuelistOfDeepFaith extends CardImpl { // As long as it's your turn, Duelist of Deep Faith has first strike. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), - MyTurnCondition.instance, "as long as it's your turn, {this} has first strike" + MyTurnCondition.instance, "during your turn, {this} has first strike" )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/f/FaithfulPikemaster.java b/Mage.Sets/src/mage/cards/f/FaithfulPikemaster.java index 7ec554a3515..a990fe5b854 100644 --- a/Mage.Sets/src/mage/cards/f/FaithfulPikemaster.java +++ b/Mage.Sets/src/mage/cards/f/FaithfulPikemaster.java @@ -37,7 +37,7 @@ public final class FaithfulPikemaster extends CardImpl { // As long as it's your turn, Faithful Pikemaster has first strike. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} has first strike." + MyTurnCondition.instance, "during your turn, {this} has first strike." )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/f/ForgeAnew.java b/Mage.Sets/src/mage/cards/f/ForgeAnew.java index 6feaac3ce53..2cbcab53136 100644 --- a/Mage.Sets/src/mage/cards/f/ForgeAnew.java +++ b/Mage.Sets/src/mage/cards/f/ForgeAnew.java @@ -47,7 +47,7 @@ public class ForgeAnew extends CardImpl { //As long as it’s your turn, you may activate equip abilities any time you could cast an instant. this.addAbility(new SimpleStaticAbility(new ConditionalAsThoughEffect( new ActivateAbilitiesAnyTimeYouCouldCastInstantEffect(EquipAbility.class, "equip abilities"), MyTurnCondition.instance - ).setText("as long as it's your turn, you may activate equip abilities any time you could cast an instant.")) + ).setText("during your turn, you may activate equip abilities any time you could cast an instant.")) ); //You may pay {0} rather than pay the equip cost of the first equip ability you activate during each of your turns. @@ -144,4 +144,4 @@ class ForgeAnewWatcher extends Watcher { ForgeAnewWatcher watcher = game.getState().getWatcher(ForgeAnewWatcher.class); return watcher != null && watcher.equippedThisTurn.contains(playerId); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java b/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java index 65e834023de..efb3a17d37b 100644 --- a/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java +++ b/Mage.Sets/src/mage/cards/f/FrodoDeterminedHero.java @@ -50,7 +50,7 @@ public final class FrodoDeterminedHero extends CardImpl { // As long as it's your turn, prevent all damage that would be dealt to Frodo. this.addAbility(new SimpleStaticAbility(new ConditionalPreventionEffect( new PreventAllDamageToSourceEffect(Duration.WhileOnBattlefield), MyTurnCondition.instance, - "as long as it's your turn, prevent all damage that would be dealt to {this}" + "during your turn, prevent all damage that would be dealt to {this}" ))); } diff --git a/Mage.Sets/src/mage/cards/g/GuardianNaga.java b/Mage.Sets/src/mage/cards/g/GuardianNaga.java index b035af53982..23ee9288db3 100644 --- a/Mage.Sets/src/mage/cards/g/GuardianNaga.java +++ b/Mage.Sets/src/mage/cards/g/GuardianNaga.java @@ -35,7 +35,7 @@ public final class GuardianNaga extends AdventureCard { // As long as it's your turn, prevent all damage that would be dealt to Guardian Naga. this.addAbility(new SimpleStaticAbility(new ConditionalPreventionEffect( new PreventAllDamageToSourceEffect(Duration.WhileOnBattlefield), MyTurnCondition.instance, - "as long as it's your turn, prevent all damage that would be dealt to {this}" + "during your turn, prevent all damage that would be dealt to {this}" ))); // Banishing Coils diff --git a/Mage.Sets/src/mage/cards/h/HexgoldHalberd.java b/Mage.Sets/src/mage/cards/h/HexgoldHalberd.java index 64c50c3941e..c3b5c746bfb 100644 --- a/Mage.Sets/src/mage/cards/h/HexgoldHalberd.java +++ b/Mage.Sets/src/mage/cards/h/HexgoldHalberd.java @@ -36,7 +36,7 @@ public final class HexgoldHalberd extends CardImpl { // As long as it's your turn, equipped creature has first strike and trample. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityAttachedEffect(FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT), - MyTurnCondition.instance, "as long as it's your turn, equipped creature has first strike" + MyTurnCondition.instance, "during your turn, equipped creature has first strike" )); ability.addEffect(new ConditionalContinuousEffect( new GainAbilityAttachedEffect(TrampleAbility.getInstance(), AttachmentType.EQUIPMENT), diff --git a/Mage.Sets/src/mage/cards/h/Hookblade.java b/Mage.Sets/src/mage/cards/h/Hookblade.java index 6d41497fa40..dccff7bdb2e 100644 --- a/Mage.Sets/src/mage/cards/h/Hookblade.java +++ b/Mage.Sets/src/mage/cards/h/Hookblade.java @@ -36,7 +36,7 @@ public final class Hookblade extends CardImpl { // As long as it's your turn, equipped creature has flying. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityAttachedEffect(FlyingAbility.getInstance().getInstance(), AttachmentType.EQUIPMENT), - MyTurnCondition.instance, "as long as it's your turn, equipped creature has flying" + MyTurnCondition.instance, "during your turn, equipped creature has flying" )).addHint(MyTurnHint.instance)); // Equip {2} diff --git a/Mage.Sets/src/mage/cards/h/HookbladeVeteran.java b/Mage.Sets/src/mage/cards/h/HookbladeVeteran.java index af280695d3c..7e280f5f9e8 100644 --- a/Mage.Sets/src/mage/cards/h/HookbladeVeteran.java +++ b/Mage.Sets/src/mage/cards/h/HookbladeVeteran.java @@ -31,7 +31,7 @@ public final class HookbladeVeteran extends CardImpl { // As long as it's your turn, Hookblade Veteran has flying. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} has flying" + MyTurnCondition.instance, "during your turn, {this} has flying" )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/j/JavelinOfLightning.java b/Mage.Sets/src/mage/cards/j/JavelinOfLightning.java index c939738da92..51e89db9cb6 100644 --- a/Mage.Sets/src/mage/cards/j/JavelinOfLightning.java +++ b/Mage.Sets/src/mage/cards/j/JavelinOfLightning.java @@ -38,7 +38,7 @@ public final class JavelinOfLightning extends CardImpl { // As long as it's your turn, equipped creature gets +2/+0 and has first strike. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostEquippedEffect(2, 0), MyTurnCondition.instance, - "as long as it's your turn, equipped creature gets +2/+0")); + "during your turn, equipped creature gets +2/+0")); ability.addEffect(new ConditionalContinuousEffect(new GainAbilityAttachedEffect( FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT ), MyTurnCondition.instance, "and has first strike")); diff --git a/Mage.Sets/src/mage/cards/k/Knife.java b/Mage.Sets/src/mage/cards/k/Knife.java index b425fa67570..2d8a4965b5b 100644 --- a/Mage.Sets/src/mage/cards/k/Knife.java +++ b/Mage.Sets/src/mage/cards/k/Knife.java @@ -31,7 +31,7 @@ public final class Knife extends CardImpl { // As long as it's your turn, equipped creature gets +1/+0 and has first strike. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostEquippedEffect(1, 0), MyTurnCondition.instance, - "as long as it's your turn, equipped creature gets +1/+0" + "during your turn, equipped creature gets +1/+0" )); ability.addEffect(new ConditionalContinuousEffect(new GainAbilityAttachedEffect( FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT diff --git a/Mage.Sets/src/mage/cards/l/LeechFanatic.java b/Mage.Sets/src/mage/cards/l/LeechFanatic.java index dd4f73ffbd0..70c1eb1097a 100644 --- a/Mage.Sets/src/mage/cards/l/LeechFanatic.java +++ b/Mage.Sets/src/mage/cards/l/LeechFanatic.java @@ -31,7 +31,7 @@ public final class LeechFanatic extends CardImpl { // As long as it's your turn, Leech Fanatic has lifelink. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(LifelinkAbility.getInstance(), Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} has lifelink" + MyTurnCondition.instance, "during your turn, {this} has lifelink" )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java b/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java index 55a388ae9f8..bae4567b8ed 100644 --- a/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java +++ b/Mage.Sets/src/mage/cards/m/MiriamHerdWhisperer.java @@ -47,7 +47,7 @@ public final class MiriamHerdWhisperer extends CardImpl { // As long as it's your turn, Mounts and Vehicles you control have hexproof. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilityAllEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield, filter), - MyTurnCondition.instance, "as long as it's your turn, Mounts and Vehicles you control have hexproof" + MyTurnCondition.instance, "during your turn, Mounts and Vehicles you control have hexproof" ))); // Whenever a Mount or Vehicle you control attacks, put a +1/+1 counter on it. diff --git a/Mage.Sets/src/mage/cards/m/MiteOverseer.java b/Mage.Sets/src/mage/cards/m/MiteOverseer.java index b1897aa6e67..23daae87013 100644 --- a/Mage.Sets/src/mage/cards/m/MiteOverseer.java +++ b/Mage.Sets/src/mage/cards/m/MiteOverseer.java @@ -41,7 +41,7 @@ public final class MiteOverseer extends CardImpl { // As long as it's your turn, creature tokens you control get +1/+0 and have first strike. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect(new BoostControlledEffect( 1, 0, Duration.WhileOnBattlefield, StaticFilters.FILTER_CREATURE_TOKEN - ), MyTurnCondition.instance, "as long as it's your turn, creature tokens you control get +1/+0")); + ), MyTurnCondition.instance, "during your turn, creature tokens you control get +1/+0")); ability.addEffect(new ConditionalContinuousEffect(new GainAbilityControlledEffect( FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_CREATURE_TOKEN ), MyTurnCondition.instance, "and have first strike")); diff --git a/Mage.Sets/src/mage/cards/p/PravaOfTheSteelLegion.java b/Mage.Sets/src/mage/cards/p/PravaOfTheSteelLegion.java index ddd95014043..20e305dba0e 100644 --- a/Mage.Sets/src/mage/cards/p/PravaOfTheSteelLegion.java +++ b/Mage.Sets/src/mage/cards/p/PravaOfTheSteelLegion.java @@ -44,7 +44,7 @@ public final class PravaOfTheSteelLegion extends CardImpl { // As long as it's your turn, creature tokens you control get +1/+4. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect(new BoostControlledEffect( 1, 4, Duration.WhileOnBattlefield, filter - ), MyTurnCondition.instance, "as long as it's your turn, creature tokens you control get +1/+4"))); + ), MyTurnCondition.instance, "during your turn, creature tokens you control get +1/+4"))); // {3}{W}: Create a 1/1 white Soldier creature token. this.addAbility(new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/s/ShaoJun.java b/Mage.Sets/src/mage/cards/s/ShaoJun.java index 29b708e7f21..f01493cd8d1 100644 --- a/Mage.Sets/src/mage/cards/s/ShaoJun.java +++ b/Mage.Sets/src/mage/cards/s/ShaoJun.java @@ -44,7 +44,7 @@ public final class ShaoJun extends CardImpl { // Leap Strike -- As long as it's your turn, Shao Jun has flying and first strike. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} has flying" + MyTurnCondition.instance, "during your turn, {this} has flying" )); ability.addEffect(new ConditionalContinuousEffect( new GainAbilitySourceEffect(FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield), diff --git a/Mage.Sets/src/mage/cards/s/SkophosReaver.java b/Mage.Sets/src/mage/cards/s/SkophosReaver.java index 88bc8571346..4b3af0f4fce 100644 --- a/Mage.Sets/src/mage/cards/s/SkophosReaver.java +++ b/Mage.Sets/src/mage/cards/s/SkophosReaver.java @@ -31,7 +31,7 @@ public final class SkophosReaver extends CardImpl { // As long as it's your turn, Skophos Reaver gets +2/+0. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostSourceEffect(2, 0, Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} gets +2/+0" + MyTurnCondition.instance, "during your turn, {this} gets +2/+0" ))); // Madness {1}{R} diff --git a/Mage.Sets/src/mage/cards/s/SporebackWolf.java b/Mage.Sets/src/mage/cards/s/SporebackWolf.java index 4829b90571e..827640ebb0b 100644 --- a/Mage.Sets/src/mage/cards/s/SporebackWolf.java +++ b/Mage.Sets/src/mage/cards/s/SporebackWolf.java @@ -29,7 +29,7 @@ public final class SporebackWolf extends CardImpl { // As long as it's your turn, Sporeback Wolf gets +0/+2. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostSourceEffect(0, 2, Duration.WhileOnBattlefield), - MyTurnCondition.instance, "as long as it's your turn, {this} gets +0/+2." + MyTurnCondition.instance, "during your turn, {this} gets +0/+2." )).addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/z/ZamrielSeraphOfSteel.java b/Mage.Sets/src/mage/cards/z/ZamrielSeraphOfSteel.java index 06bfb82eccc..74d3ba631d0 100644 --- a/Mage.Sets/src/mage/cards/z/ZamrielSeraphOfSteel.java +++ b/Mage.Sets/src/mage/cards/z/ZamrielSeraphOfSteel.java @@ -47,7 +47,7 @@ public final class ZamrielSeraphOfSteel extends CardImpl { new GainAbilityControlledEffect( IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield, filter - ), MyTurnCondition.instance, "as long as it's your turn, " + + ), MyTurnCondition.instance, "during your turn, " + "equipped creatures you control have indestructible" )).addHint(MyTurnHint.instance)); } -- 2.47.2 From 6f43e76c9eeb46286ef002604670d8888602c4e8 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 29 Nov 2024 02:47:51 -0500 Subject: [PATCH 24/40] followup text fixes --- Mage.Sets/src/mage/cards/g/GeyserDrake.java | 2 +- .../mage/abilities/effects/common/SacrificeAllEffect.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/g/GeyserDrake.java b/Mage.Sets/src/mage/cards/g/GeyserDrake.java index dcc23648826..4d17f62214c 100644 --- a/Mage.Sets/src/mage/cards/g/GeyserDrake.java +++ b/Mage.Sets/src/mage/cards/g/GeyserDrake.java @@ -32,7 +32,7 @@ public final class GeyserDrake extends CardImpl { // As long as it's not your turn, spells you cast cost {1} less to cast. this.addAbility(new SimpleStaticAbility(new ConditionalCostModificationEffect( new SpellsCostReductionControllerEffect(StaticFilters.FILTER_CARD, 1), - NotMyTurnCondition.instance, "as long as it's not your turn, " + + NotMyTurnCondition.instance, "during turns other than yours, " + "spells you cast cost {1} less to cast" ))); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java index d6a300d9fa1..d426ccf9627 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/SacrificeAllEffect.java @@ -139,7 +139,9 @@ public class SacrificeAllEffect extends OneShotEffect { sb.append(' '); sb.append(filter.getMessage()); } - sb.append(" of their choice"); + if (!filter.getMessage().contains("with")) { + sb.append(" of their choice"); + } staticText = sb.toString(); } -- 2.47.2 From ab81e45cc9659a1b273b915766a7ad6bf89790a8 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 29 Nov 2024 02:50:09 -0500 Subject: [PATCH 25/40] text update: "of their choice" of the top or bottom --- Mage.Sets/src/mage/cards/a/AetherGust.java | 2 +- Mage.Sets/src/mage/cards/u/UnchartedVoyage.java | 2 +- .../common/PutOnTopOrBottomLibraryTargetEffect.java | 11 +---------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AetherGust.java b/Mage.Sets/src/mage/cards/a/AetherGust.java index a12817aa909..fc0254699f6 100644 --- a/Mage.Sets/src/mage/cards/a/AetherGust.java +++ b/Mage.Sets/src/mage/cards/a/AetherGust.java @@ -37,7 +37,7 @@ public final class AetherGust extends CardImpl { // Choose target spell or permanent that's red or green. Its owner puts it on the top or bottom of their library. this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect(true).setText( "choose target spell or permanent that's red or green. " + - "Its owner puts it on the top or bottom of their library" + "Its owner puts it on their choice of the top or bottom of their library" )); this.getSpellAbility().addTarget(new TargetSpellOrPermanent(filter)); } diff --git a/Mage.Sets/src/mage/cards/u/UnchartedVoyage.java b/Mage.Sets/src/mage/cards/u/UnchartedVoyage.java index c6d5ca882f0..f3e6c98c98d 100644 --- a/Mage.Sets/src/mage/cards/u/UnchartedVoyage.java +++ b/Mage.Sets/src/mage/cards/u/UnchartedVoyage.java @@ -18,7 +18,7 @@ public final class UnchartedVoyage extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}"); // Target creature's owner puts it on their choice of the top or bottom of their library. - this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect(false, true)); + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect(false)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); // Surveil 1. diff --git a/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java index 3a9b6e9092c..4a99ba55bd2 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java @@ -13,24 +13,15 @@ import mage.players.Player; public class PutOnTopOrBottomLibraryTargetEffect extends OneShotEffect { private final boolean textOwnerOf; - private final boolean textTheirChoice; public PutOnTopOrBottomLibraryTargetEffect(boolean textOwnerOf) { super(Outcome.ReturnToHand); this.textOwnerOf = textOwnerOf; - this.textTheirChoice = false; - } - - public PutOnTopOrBottomLibraryTargetEffect(boolean textOwnerOf, boolean textTheirChoice) { - super(Outcome.ReturnToHand); - this.textOwnerOf = textOwnerOf; - this.textTheirChoice = textTheirChoice; } private PutOnTopOrBottomLibraryTargetEffect(final PutOnTopOrBottomLibraryTargetEffect effect) { super(effect); this.textOwnerOf = effect.textOwnerOf; - this.textTheirChoice = effect.textTheirChoice; } @Override @@ -58,6 +49,6 @@ public class PutOnTopOrBottomLibraryTargetEffect extends OneShotEffect { } String targetText = getTargetPointer().describeTargets(mode.getTargets(), "that permanent"); return (textOwnerOf ? "the owner of " + targetText : targetText + "'s owner") + - " puts it on " + (textTheirChoice ? "their choice of " : "") + "the top or bottom of their library"; + " puts it on their choice of the top or bottom of their library"; } } -- 2.47.2 From d12b34e05bacb043581544855f7c60027e538c90 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 29 Nov 2024 02:55:02 -0500 Subject: [PATCH 26/40] a few more text fixes --- Mage.Sets/src/mage/cards/p/PickYourPoison.java | 3 ++- Mage.Sets/src/mage/cards/p/PompousGadabout.java | 4 ++-- Mage.Sets/src/mage/cards/w/WildwoodGeist.java | 3 +-- Mage.Sets/src/mage/cards/z/ZurgoHelmsmasher.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Mage.Sets/src/mage/cards/p/PickYourPoison.java b/Mage.Sets/src/mage/cards/p/PickYourPoison.java index b7ec2909e29..fa624d5439e 100644 --- a/Mage.Sets/src/mage/cards/p/PickYourPoison.java +++ b/Mage.Sets/src/mage/cards/p/PickYourPoison.java @@ -35,7 +35,8 @@ public final class PickYourPoison extends CardImpl { this.getSpellAbility().addMode(new Mode(new SacrificeOpponentsEffect(StaticFilters.FILTER_PERMANENT_ENCHANTMENT))); // * Each opponent sacrifices a creature with flying. - this.getSpellAbility().addMode(new Mode(new SacrificeOpponentsEffect(filter))); + this.getSpellAbility().addMode(new Mode(new SacrificeOpponentsEffect(filter) + .setText("each opponent sacrifices a creature of their choice with flying"))); } private PickYourPoison(final PickYourPoison card) { diff --git a/Mage.Sets/src/mage/cards/p/PompousGadabout.java b/Mage.Sets/src/mage/cards/p/PompousGadabout.java index fbb62bc9219..d62ccf93994 100644 --- a/Mage.Sets/src/mage/cards/p/PompousGadabout.java +++ b/Mage.Sets/src/mage/cards/p/PompousGadabout.java @@ -43,7 +43,7 @@ public final class PompousGadabout extends CardImpl { // Pompous Gadabout has hexproof as long as it's your turn. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(HexproofAbility.getInstance(), Duration.WhileOnBattlefield), - MyTurnCondition.instance, "{this} has hexproof as long as it's your turn" + MyTurnCondition.instance, "during your turn, {this} has hexproof" )).addHint(MyTurnHint.instance)); // Pompous Gadabout can't be blocked by creatures that don't have a name. @@ -67,4 +67,4 @@ enum PompousGadaboutPredicate implements Predicate { public boolean apply(Permanent input, Game game) { return input.getName() == null || input.getName().isEmpty(); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/w/WildwoodGeist.java b/Mage.Sets/src/mage/cards/w/WildwoodGeist.java index 2b8aece07af..48bac927307 100644 --- a/Mage.Sets/src/mage/cards/w/WildwoodGeist.java +++ b/Mage.Sets/src/mage/cards/w/WildwoodGeist.java @@ -11,7 +11,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; -import mage.constants.Zone; import java.util.UUID; @@ -31,7 +30,7 @@ public final class WildwoodGeist extends CardImpl { this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new BoostSourceEffect(2, 2, Duration.WhileOnBattlefield), MyTurnCondition.instance, - "{this} gets +2/+2 as long as it's your turn")) + "during your turn, {this} gets +2/+2")) .addHint(MyTurnHint.instance)); } diff --git a/Mage.Sets/src/mage/cards/z/ZurgoHelmsmasher.java b/Mage.Sets/src/mage/cards/z/ZurgoHelmsmasher.java index 9ca7ced514d..1768be75d1c 100644 --- a/Mage.Sets/src/mage/cards/z/ZurgoHelmsmasher.java +++ b/Mage.Sets/src/mage/cards/z/ZurgoHelmsmasher.java @@ -41,7 +41,7 @@ public final class ZurgoHelmsmasher extends CardImpl { this.addAbility(new SimpleStaticAbility( new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), MyTurnCondition.instance, - "{this} has indestructible as long as it's your turn")) + "during your turn, {this} has indestructible")) .addHint(MyTurnHint.instance)); // Whenever a creature dealt damage by Zurgo Helmsmasher this turn dies, put a +1/+1 counter on Zurgo Helmsmasher. -- 2.47.2 From 4edce7673295c10bf0bc7a266ab6cda8dec6309c Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 29 Nov 2024 03:07:41 -0500 Subject: [PATCH 27/40] refactor: simplify DistributeCountersEffect constructors --- Mage.Sets/src/mage/cards/a/AbzanCharm.java | 2 +- .../src/mage/cards/a/AjaniMentorOfHeroes.java | 2 +- .../src/mage/cards/a/AjaniSleeperAgent.java | 2 +- Mage.Sets/src/mage/cards/a/ArmamentCorps.java | 2 +- .../src/mage/cards/b/BiogenicUpgrade.java | 4 +-- .../src/mage/cards/b/BlessingsOfNature.java | 2 +- .../src/mage/cards/b/BountyOfTheHunt.java | 4 +-- .../mage/cards/c/CaseOfTheTrampledGarden.java | 2 +- Mage.Sets/src/mage/cards/c/Contagion.java | 2 +- .../src/mage/cards/c/CourtOfGarenbrig.java | 2 +- .../src/mage/cards/d/DefendTheCelestus.java | 2 +- Mage.Sets/src/mage/cards/e/ElusiveOtter.java | 2 +- Mage.Sets/src/mage/cards/e/ElvenRite.java | 2 +- Mage.Sets/src/mage/cards/g/GlintWeaver.java | 2 +- .../src/mage/cards/j/JadeSeedstones.java | 2 +- .../src/mage/cards/j/JuganTheRisingStar.java | 2 +- .../mage/cards/l/LathielTheBounteousDawn.java | 2 +- .../mage/cards/m/MyojinOfToweringMight.java | 2 +- .../src/mage/cards/n/NumaJoragaChieftain.java | 2 +- .../src/mage/cards/o/OnduKnotmaster.java | 2 +- Mage.Sets/src/mage/cards/p/PicnicRuiner.java | 2 +- .../src/mage/cards/q/QuirionBeastcaller.java | 2 +- .../src/mage/cards/s/ShamblingSwarm.java | 4 ++- Mage.Sets/src/mage/cards/s/SplendidAgony.java | 2 +- .../src/mage/cards/s/StormTheSeedcore.java | 2 +- .../src/mage/cards/t/TheGrandEvolution.java | 2 +- .../src/mage/cards/u/UndercityUpheaval.java | 2 +- .../mage/cards/u/UrzaAcademyHeadmaster.java | 2 +- .../src/mage/cards/v/VerdurousGearhulk.java | 2 +- .../src/mage/cards/v/VivienArkbowRanger.java | 2 +- .../src/mage/cards/w/WurmskinForger.java | 2 +- .../cards/y/YannikScavengingSentinel.java | 5 ++-- .../counter/DistributeCountersEffect.java | 26 ++++++++++++------- 33 files changed, 53 insertions(+), 46 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AbzanCharm.java b/Mage.Sets/src/mage/cards/a/AbzanCharm.java index f34b01dd664..0de055f435a 100644 --- a/Mage.Sets/src/mage/cards/a/AbzanCharm.java +++ b/Mage.Sets/src/mage/cards/a/AbzanCharm.java @@ -43,7 +43,7 @@ public final class AbzanCharm extends CardImpl { this.getSpellAbility().addMode(mode); // *Distribute two +1/+1 counters among one or two target creatures. - mode = new Mode(new DistributeCountersEffect(CounterType.P1P1, 2, false, "one or two target creatures")); + mode = new Mode(new DistributeCountersEffect(2, "one or two target creatures")); mode.addTarget(new TargetCreaturePermanentAmount(2)); this.getSpellAbility().addMode(mode); diff --git a/Mage.Sets/src/mage/cards/a/AjaniMentorOfHeroes.java b/Mage.Sets/src/mage/cards/a/AjaniMentorOfHeroes.java index 7015d62397e..02796fb08c5 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniMentorOfHeroes.java +++ b/Mage.Sets/src/mage/cards/a/AjaniMentorOfHeroes.java @@ -38,7 +38,7 @@ public final class AjaniMentorOfHeroes extends CardImpl { this.setStartingLoyalty(4); // +1: Distribute three +1/+1 counters among one, two, or three target creatures you control - Ability ability = new LoyaltyAbility(new DistributeCountersEffect(CounterType.P1P1, 3, false, "one, two, or three target creatures you control"), 1); + Ability ability = new LoyaltyAbility(new DistributeCountersEffect(3, "one, two, or three target creatures you control"), 1); ability.addTarget(new TargetCreaturePermanentAmount(3, StaticFilters.FILTER_CONTROLLED_CREATURES)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/a/AjaniSleeperAgent.java b/Mage.Sets/src/mage/cards/a/AjaniSleeperAgent.java index 1307aed6550..091d653edea 100644 --- a/Mage.Sets/src/mage/cards/a/AjaniSleeperAgent.java +++ b/Mage.Sets/src/mage/cards/a/AjaniSleeperAgent.java @@ -42,7 +42,7 @@ public final class AjaniSleeperAgent extends CardImpl { this.addAbility(new LoyaltyAbility(new AjaniSleeperAgentEffect(), 1)); // −3: Distribute three +1/+1 counters among up to three target creatures. They gain vigilance until end of turn. - Ability ability = new LoyaltyAbility(new DistributeCountersEffect(CounterType.P1P1, 3, false, "up to three target creatures"), -3); + Ability ability = new LoyaltyAbility(new DistributeCountersEffect(3, "up to three target creatures"), -3); ability.addEffect(new GainAbilityTargetEffect(VigilanceAbility.getInstance()).setText("They gain vigilance until end of turn")); Target target = new TargetCreaturePermanentAmount(3); target.setMinNumberOfTargets(0); diff --git a/Mage.Sets/src/mage/cards/a/ArmamentCorps.java b/Mage.Sets/src/mage/cards/a/ArmamentCorps.java index dd6f95e5014..454359f62e7 100644 --- a/Mage.Sets/src/mage/cards/a/ArmamentCorps.java +++ b/Mage.Sets/src/mage/cards/a/ArmamentCorps.java @@ -28,7 +28,7 @@ public final class ArmamentCorps extends CardImpl { this.toughness = new MageInt(4); // When Armament Corps enters the battlefield, distribute two +1/+1 counters among one or two target creatures you control. - Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 2, false, "one or two target creatures you control"), false); + Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(2, "one or two target creatures you control"), false); ability.addTarget(new TargetCreaturePermanentAmount(2, StaticFilters.FILTER_CONTROLLED_CREATURES)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/b/BiogenicUpgrade.java b/Mage.Sets/src/mage/cards/b/BiogenicUpgrade.java index 8d0622f6e4d..af5253deaee 100644 --- a/Mage.Sets/src/mage/cards/b/BiogenicUpgrade.java +++ b/Mage.Sets/src/mage/cards/b/BiogenicUpgrade.java @@ -27,7 +27,7 @@ public final class BiogenicUpgrade extends CardImpl { // Distribute three +1/+1 counters among one, two, or three target creatures, then double the number of +1/+1 counters on each of those creatures. this.getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.P1P1, 3, false, + 3, "one, two, or three target creatures" )); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(3)); @@ -75,4 +75,4 @@ class BiogenicUpgradeEffect extends OneShotEffect { } return true; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java b/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java index 0dd2185b863..b6f13ca0c49 100644 --- a/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java +++ b/Mage.Sets/src/mage/cards/b/BlessingsOfNature.java @@ -19,7 +19,7 @@ public final class BlessingsOfNature extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{G}"); // Distribute four +1/+1 counters among any number of target creatures. - this.getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.P1P1, 4, false, "any number of target creatures")); + this.getSpellAbility().addEffect(new DistributeCountersEffect(4, "any number of target creatures")); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(4)); this.addAbility(new MiracleAbility("{G}")); diff --git a/Mage.Sets/src/mage/cards/b/BountyOfTheHunt.java b/Mage.Sets/src/mage/cards/b/BountyOfTheHunt.java index 6def70de482..082ba4df231 100644 --- a/Mage.Sets/src/mage/cards/b/BountyOfTheHunt.java +++ b/Mage.Sets/src/mage/cards/b/BountyOfTheHunt.java @@ -35,9 +35,9 @@ public final class BountyOfTheHunt extends CardImpl { // Distribute three +1/+1 counters among one, two, or three target creatures. For each +1/+1 counter you put on a creature this way, remove a +1/+1 counter from that creature at the beginning of the next cleanup step. this.getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.P1P1, 3, true, + 3, "one, two, or three target creatures" - )); + ).withRemoveAtEndOfTurn()); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(3)); } diff --git a/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java b/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java index fefa7177521..bc7153a94ce 100644 --- a/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java +++ b/Mage.Sets/src/mage/cards/c/CaseOfTheTrampledGarden.java @@ -40,7 +40,7 @@ public final class CaseOfTheTrampledGarden extends CardImpl { this.subtype.add(SubType.CASE); // When this Case enters the battlefield, distribute two +1/+1 counters among one or two target creatures you control. - Ability initialAbility = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 2, + Ability initialAbility = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(2, "one or two target creatures you control")); TargetPermanentAmount target = new TargetPermanentAmount(2, StaticFilters.FILTER_CONTROLLED_CREATURES); target.setMinNumberOfTargets(1); diff --git a/Mage.Sets/src/mage/cards/c/Contagion.java b/Mage.Sets/src/mage/cards/c/Contagion.java index c1e223c9079..0e27f3f1b90 100644 --- a/Mage.Sets/src/mage/cards/c/Contagion.java +++ b/Mage.Sets/src/mage/cards/c/Contagion.java @@ -39,7 +39,7 @@ public final class Contagion extends CardImpl { // Distribute two -2/-1 counters among one or two target creatures. this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(2)); this.getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.M2M1, 2, false, + CounterType.M2M1, 2, "one or two target creatures" )); } diff --git a/Mage.Sets/src/mage/cards/c/CourtOfGarenbrig.java b/Mage.Sets/src/mage/cards/c/CourtOfGarenbrig.java index f932580a229..bb9072ea778 100644 --- a/Mage.Sets/src/mage/cards/c/CourtOfGarenbrig.java +++ b/Mage.Sets/src/mage/cards/c/CourtOfGarenbrig.java @@ -33,7 +33,7 @@ public final class CourtOfGarenbrig extends CardImpl { // At the beginning of your upkeep, distribute two +1/+1 counters among up to two target creatures. Then if you're the monarch, double the number of +1/+1 counters on each creature you control. Ability ability = new BeginningOfUpkeepTriggeredAbility( new DistributeCountersEffect( - CounterType.P1P1, 2, false, "up to two target creatures" + 2, "up to two target creatures" ) ); TargetCreaturePermanentAmount target = new TargetCreaturePermanentAmount(2); diff --git a/Mage.Sets/src/mage/cards/d/DefendTheCelestus.java b/Mage.Sets/src/mage/cards/d/DefendTheCelestus.java index 6aaf9f372af..cf520b7eb23 100644 --- a/Mage.Sets/src/mage/cards/d/DefendTheCelestus.java +++ b/Mage.Sets/src/mage/cards/d/DefendTheCelestus.java @@ -20,7 +20,7 @@ public final class DefendTheCelestus extends CardImpl { // Distribute three +1/+1 counters among one, two, or three target creatures you control. this.getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.P1P1, 3, false, + 3, "one, two, or three target creatures you control" )); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount( diff --git a/Mage.Sets/src/mage/cards/e/ElusiveOtter.java b/Mage.Sets/src/mage/cards/e/ElusiveOtter.java index bd427e0393a..fdc288de3bd 100644 --- a/Mage.Sets/src/mage/cards/e/ElusiveOtter.java +++ b/Mage.Sets/src/mage/cards/e/ElusiveOtter.java @@ -38,7 +38,7 @@ public final class ElusiveOtter extends AdventureCard { // Grove's Bounty // Distribute X +1/+1 counters among any number of target creatures you control. this.getSpellCard().getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.P1P1, GetXValue.instance, false, + CounterType.P1P1, GetXValue.instance, "any number of target creatures you control" )); Target target = new TargetCreaturePermanentAmount(GetXValue.instance, StaticFilters.FILTER_CONTROLLED_CREATURES); diff --git a/Mage.Sets/src/mage/cards/e/ElvenRite.java b/Mage.Sets/src/mage/cards/e/ElvenRite.java index 9acd8a00d2c..030277f47db 100644 --- a/Mage.Sets/src/mage/cards/e/ElvenRite.java +++ b/Mage.Sets/src/mage/cards/e/ElvenRite.java @@ -19,7 +19,7 @@ public final class ElvenRite extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{G}"); // Distribute two +1/+1 counters among one or two target creatures. - this.getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.P1P1, 2, false, "one or two target creatures")); + this.getSpellAbility().addEffect(new DistributeCountersEffect(2, "one or two target creatures")); this.getSpellAbility().addTarget(new TargetCreaturePermanentAmount(2)); } diff --git a/Mage.Sets/src/mage/cards/g/GlintWeaver.java b/Mage.Sets/src/mage/cards/g/GlintWeaver.java index 1a9ab59f620..2d8f0e0d398 100644 --- a/Mage.Sets/src/mage/cards/g/GlintWeaver.java +++ b/Mage.Sets/src/mage/cards/g/GlintWeaver.java @@ -33,7 +33,7 @@ public final class GlintWeaver extends CardImpl { // When Glint Weaver enters the battlefield, distribute three +1/+1 counters among one, two, or three target creatures, then you gain life equal to the greatest toughness among creatures you control. Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect( - CounterType.P1P1, 3, "one, two, or three target creatures" + 3, "one, two, or three target creatures" )); ability.addEffect(new GainLifeEffect(GreatestToughnessAmongControlledCreaturesValue.instance) .setText(", then you gain life equal to the greatest toughness among creatures you control")); diff --git a/Mage.Sets/src/mage/cards/j/JadeSeedstones.java b/Mage.Sets/src/mage/cards/j/JadeSeedstones.java index 837679c29fa..149827d2016 100644 --- a/Mage.Sets/src/mage/cards/j/JadeSeedstones.java +++ b/Mage.Sets/src/mage/cards/j/JadeSeedstones.java @@ -24,7 +24,7 @@ public final class JadeSeedstones extends CardImpl { // When Jade Seedstones enters the battlefield, distribute three +1/+1 counters among one, two, or three target creatures you control. Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect( - CounterType.P1P1, 3, "one, two, or three target creatures you control" + 3, "one, two, or three target creatures you control" )); TargetPermanentAmount target = new TargetPermanentAmount(3, StaticFilters.FILTER_CONTROLLED_CREATURES); target.setMinNumberOfTargets(1); diff --git a/Mage.Sets/src/mage/cards/j/JuganTheRisingStar.java b/Mage.Sets/src/mage/cards/j/JuganTheRisingStar.java index 192819795c9..effc7db6288 100644 --- a/Mage.Sets/src/mage/cards/j/JuganTheRisingStar.java +++ b/Mage.Sets/src/mage/cards/j/JuganTheRisingStar.java @@ -33,7 +33,7 @@ public final class JuganTheRisingStar extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // When Jugan, the Rising Star dies, you may distribute five +1/+1 counters among any number of target creatures. - Ability ability = new DiesSourceTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 5, false, "any number of target creatures"), true); + Ability ability = new DiesSourceTriggeredAbility(new DistributeCountersEffect(5, "any number of target creatures"), true); ability.addTarget(new TargetCreaturePermanentAmount(5)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/l/LathielTheBounteousDawn.java b/Mage.Sets/src/mage/cards/l/LathielTheBounteousDawn.java index f78d7464be6..47bf24fade8 100644 --- a/Mage.Sets/src/mage/cards/l/LathielTheBounteousDawn.java +++ b/Mage.Sets/src/mage/cards/l/LathielTheBounteousDawn.java @@ -45,7 +45,7 @@ public final class LathielTheBounteousDawn extends CardImpl { // At the beginning of each end step, if you gained life this turn, distribute up to that many +1/+1 counters among any number of other target creatures. Ability ability = new ConditionalInterveningIfTriggeredAbility( new BeginningOfEndStepTriggeredAbility(TargetController.ANY, new DistributeCountersEffect( - CounterType.P1P1, 1, false, "" + 1, "" ), false), condition, "At the beginning of each end step, if you gained life this turn, " + "distribute up to that many +1/+1 counters among any number of other target creatures." diff --git a/Mage.Sets/src/mage/cards/m/MyojinOfToweringMight.java b/Mage.Sets/src/mage/cards/m/MyojinOfToweringMight.java index 22ebbcd5ea3..7191e76fad9 100644 --- a/Mage.Sets/src/mage/cards/m/MyojinOfToweringMight.java +++ b/Mage.Sets/src/mage/cards/m/MyojinOfToweringMight.java @@ -45,7 +45,7 @@ public final class MyojinOfToweringMight extends CardImpl { // Remove an indestructible counter from Myojin of Towering Might: Distribute eight +1/+1 counters among any number of target creatures you control. They gain trample until end of turn. Ability ability = new SimpleActivatedAbility(new DistributeCountersEffect( - CounterType.P1P1, 8, false, + 8, "any number of target creatures you control" ), new RemoveCountersSourceCost(CounterType.INDESTRUCTIBLE.createInstance())); ability.addEffect(new GainAbilityTargetEffect( diff --git a/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java b/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java index 8f128de6df0..16dc5790cdd 100644 --- a/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java +++ b/Mage.Sets/src/mage/cards/n/NumaJoragaChieftain.java @@ -88,7 +88,7 @@ class NumaJoragaChieftainEffect extends OneShotEffect { return false; } ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( - new DistributeCountersEffect(CounterType.P1P1, costX, false, ""), + new DistributeCountersEffect(costX, ""), false, "distribute " + costX + " +1/+1 counters among any number of target Elves" ); ability.addTarget(new TargetCreaturePermanentAmount(costX, filter)); diff --git a/Mage.Sets/src/mage/cards/o/OnduKnotmaster.java b/Mage.Sets/src/mage/cards/o/OnduKnotmaster.java index 3fadb3e5c83..421ac8229eb 100644 --- a/Mage.Sets/src/mage/cards/o/OnduKnotmaster.java +++ b/Mage.Sets/src/mage/cards/o/OnduKnotmaster.java @@ -52,7 +52,7 @@ public final class OnduKnotmaster extends AdventureCard { // Distribute two +1/+1 counters among one or two target creatures. this.getSpellCard().getSpellAbility().addEffect( new DistributeCountersEffect( - CounterType.P1P1, 2, false, + 2, "one or two target creatures" ) ); diff --git a/Mage.Sets/src/mage/cards/p/PicnicRuiner.java b/Mage.Sets/src/mage/cards/p/PicnicRuiner.java index d7eeb6c0ab7..e8ef4ec3cd9 100644 --- a/Mage.Sets/src/mage/cards/p/PicnicRuiner.java +++ b/Mage.Sets/src/mage/cards/p/PicnicRuiner.java @@ -42,7 +42,7 @@ public final class PicnicRuiner extends AdventureCard { // Distribute three +1/+1 counters among any number of target creatures you control. this.getSpellCard().getSpellAbility().addEffect( new DistributeCountersEffect( - CounterType.P1P1, 3, false, + 3, "any number of target creatures you control" ) ); diff --git a/Mage.Sets/src/mage/cards/q/QuirionBeastcaller.java b/Mage.Sets/src/mage/cards/q/QuirionBeastcaller.java index f246f6aaf5a..3cb8f79460c 100644 --- a/Mage.Sets/src/mage/cards/q/QuirionBeastcaller.java +++ b/Mage.Sets/src/mage/cards/q/QuirionBeastcaller.java @@ -40,7 +40,7 @@ public final class QuirionBeastcaller extends CardImpl { // When Quirion Beastcaller dies, distribute X +1/+1 counters among any number of target creatures you control, where X is the number of +1/+1 counters on Quirion Beastcaller. Ability ability = new DiesSourceTriggeredAbility(new DistributeCountersEffect( // Amount here is only used for text generation. Real amount is set in target. - CounterType.P1P1, 1, false, "any number of target creatures you control" + 1, "any number of target creatures you control" ).setText("distribute X +1/+1 counters among any number of target creatures you control, where X is the number of +1/+1 counters on {this}")); ability.addTarget(new TargetPermanentAmount(new CountersSourceCount(CounterType.P1P1), StaticFilters.FILTER_CONTROLLED_CREATURES)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/s/ShamblingSwarm.java b/Mage.Sets/src/mage/cards/s/ShamblingSwarm.java index 759c1ee3f0c..d3f9dc9c8f9 100644 --- a/Mage.Sets/src/mage/cards/s/ShamblingSwarm.java +++ b/Mage.Sets/src/mage/cards/s/ShamblingSwarm.java @@ -26,7 +26,9 @@ public final class ShamblingSwarm extends CardImpl { this.toughness = new MageInt(3); // When Shambling Swarm dies, distribute three -1/-1 counters among one, two, or three target creatures. For each -1/-1 counter you put on a creature this way, remove a -1/-1 counter from that creature at the beginning of the next end step. - Ability ability = new DiesSourceTriggeredAbility(new DistributeCountersEffect(CounterType.M1M1, 3, true, "one, two, or three target creatures"), false); + Ability ability = new DiesSourceTriggeredAbility(new DistributeCountersEffect( + CounterType.M1M1, 3, "one, two, or three target creatures" + ).withRemoveAtEndOfTurn(), false); ability.addTarget(new TargetCreaturePermanentAmount(3)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SplendidAgony.java b/Mage.Sets/src/mage/cards/s/SplendidAgony.java index 0ed6e347ff0..b553a39d4a7 100644 --- a/Mage.Sets/src/mage/cards/s/SplendidAgony.java +++ b/Mage.Sets/src/mage/cards/s/SplendidAgony.java @@ -19,7 +19,7 @@ public final class SplendidAgony extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); // Distribute two -1/-1 counters among one or two target creatures. - getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.M1M1, 2, false, "one or two target creatures")); + getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.M1M1, 2, "one or two target creatures")); getSpellAbility().addTarget(new TargetCreaturePermanentAmount(2)); } diff --git a/Mage.Sets/src/mage/cards/s/StormTheSeedcore.java b/Mage.Sets/src/mage/cards/s/StormTheSeedcore.java index bc6757909fc..39433a1b5d7 100644 --- a/Mage.Sets/src/mage/cards/s/StormTheSeedcore.java +++ b/Mage.Sets/src/mage/cards/s/StormTheSeedcore.java @@ -25,7 +25,7 @@ public final class StormTheSeedcore extends CardImpl { // Distribute four +1/+1 counter among up to four target creatures you control. Creatures you control gain vigilance and trample until end of turn. this.getSpellAbility().addEffect(new DistributeCountersEffect( - CounterType.P1P1, 4, false, + 4, "up to four target creatures you control" )); TargetPermanentAmount target = new TargetCreaturePermanentAmount(4, StaticFilters.FILTER_CONTROLLED_CREATURES); diff --git a/Mage.Sets/src/mage/cards/t/TheGrandEvolution.java b/Mage.Sets/src/mage/cards/t/TheGrandEvolution.java index 9f79f79efca..49ae80faade 100644 --- a/Mage.Sets/src/mage/cards/t/TheGrandEvolution.java +++ b/Mage.Sets/src/mage/cards/t/TheGrandEvolution.java @@ -46,7 +46,7 @@ public final class TheGrandEvolution extends CardImpl { sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_II, new DistributeCountersEffect( - CounterType.P1P1, 7, + 7, "any number of target creatures you control" ), new TargetPermanentAmount(7, StaticFilters.FILTER_CONTROLLED_CREATURES) ); diff --git a/Mage.Sets/src/mage/cards/u/UndercityUpheaval.java b/Mage.Sets/src/mage/cards/u/UndercityUpheaval.java index 9129b3df8a7..ea27f64da27 100644 --- a/Mage.Sets/src/mage/cards/u/UndercityUpheaval.java +++ b/Mage.Sets/src/mage/cards/u/UndercityUpheaval.java @@ -30,7 +30,7 @@ public final class UndercityUpheaval extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}{G}"); // Undergrowth -- Distribute X +1/+1 counters among any number of target creatures you control, where X is the number of creature cards in your graveyard as you cast this spell. Creatures you control gain vigilance until end of turn. - this.getSpellAbility().addEffect(new DistributeCountersEffect(CounterType.P1P1, 1, "") + this.getSpellAbility().addEffect(new DistributeCountersEffect(1, "") .setText("distribute X +1/+1 counters among any number of target creatures you control, " + "where X is the number of creature cards in your graveyard as you cast this spell")); this.getSpellAbility().addEffect(new GainAbilityControlledEffect( diff --git a/Mage.Sets/src/mage/cards/u/UrzaAcademyHeadmaster.java b/Mage.Sets/src/mage/cards/u/UrzaAcademyHeadmaster.java index 0d05ae0f168..6032b4ee8c1 100644 --- a/Mage.Sets/src/mage/cards/u/UrzaAcademyHeadmaster.java +++ b/Mage.Sets/src/mage/cards/u/UrzaAcademyHeadmaster.java @@ -144,7 +144,7 @@ class UrzaAcademyHeadmasterRandomEffect extends OneShotEffect { break; case 2: // AJANI MENTOR OF HEROES 1 sb.append("Distribute three +1/+1 counters among one, two, or three target creatures you control."); - effects.add(new DistributeCountersEffect(CounterType.P1P1, 3, false, "one, two, or three target creatures you control")); + effects.add(new DistributeCountersEffect(3, "one, two, or three target creatures you control")); target = new TargetCreaturePermanentAmount(3, filter1); break; case 3: // NICOL BOLAS PLANESWALKER 1 diff --git a/Mage.Sets/src/mage/cards/v/VerdurousGearhulk.java b/Mage.Sets/src/mage/cards/v/VerdurousGearhulk.java index f0b0a04f93c..b17752c6191 100644 --- a/Mage.Sets/src/mage/cards/v/VerdurousGearhulk.java +++ b/Mage.Sets/src/mage/cards/v/VerdurousGearhulk.java @@ -31,7 +31,7 @@ public final class VerdurousGearhulk extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // When Verdurous Gearhulk enters the battlefield, distribute four +1/+1 counters among any number of target creatures you control. - Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 4, false, "any number of target creatures you control"), false); + Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(4, "any number of target creatures you control"), false); ability.addTarget(new TargetCreaturePermanentAmount(4, StaticFilters.FILTER_CONTROLLED_CREATURES)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java b/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java index 699ff98981f..44b54206a5a 100644 --- a/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java +++ b/Mage.Sets/src/mage/cards/v/VivienArkbowRanger.java @@ -36,7 +36,7 @@ public final class VivienArkbowRanger extends CardImpl { // +1: Distribute two +1/+1 counters among up to two target creatures. They gain trample until end of turn. Ability ability = new LoyaltyAbility(new DistributeCountersEffect( - CounterType.P1P1, 2, false, "up to two target creatures"), 1); + 2, "up to two target creatures"), 1); ability.addEffect(new GainAbilityTargetEffect( TrampleAbility.getInstance(), Duration.EndOfTurn, "They gain trample until end of turn" diff --git a/Mage.Sets/src/mage/cards/w/WurmskinForger.java b/Mage.Sets/src/mage/cards/w/WurmskinForger.java index a76efa49449..88a1b282c0d 100644 --- a/Mage.Sets/src/mage/cards/w/WurmskinForger.java +++ b/Mage.Sets/src/mage/cards/w/WurmskinForger.java @@ -27,7 +27,7 @@ public final class WurmskinForger extends CardImpl { this.toughness = new MageInt(2); // When Wurmskin Forger enters the battlefield, distribute three +1/+1 counters among one, two, or three target creatures. - Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(CounterType.P1P1, 3, false, "one, two, or three target creatures"), false); + Ability ability = new EntersBattlefieldTriggeredAbility(new DistributeCountersEffect(3, "one, two, or three target creatures"), false); ability.addTarget(new TargetCreaturePermanentAmount(3)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java b/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java index 1bcfeadfce7..648aa7cecc1 100644 --- a/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java +++ b/Mage.Sets/src/mage/cards/y/YannikScavengingSentinel.java @@ -99,9 +99,8 @@ class YannikScavengingSentinelEffect extends OneShotEffect { game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(), source); if (game.getState().getZone(permanent.getId()) != Zone.BATTLEFIELD) { ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility( - new DistributeCountersEffect( - CounterType.P1P1, power, false, "" - ), false, "distribute X +1/+1 counters among any number of target creatures, " + + new DistributeCountersEffect(power, ""), false, + "distribute X +1/+1 counters among any number of target creatures, " + "where X is the exiled creature's power" ); ability.addTarget(new TargetCreaturePermanentAmount(power)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/DistributeCountersEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/DistributeCountersEffect.java index 0dd023ffcb8..d1ee03ce613 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/counter/DistributeCountersEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/counter/DistributeCountersEffect.java @@ -1,4 +1,3 @@ - package mage.abilities.effects.common.counter; import mage.abilities.Ability; @@ -24,22 +23,24 @@ public class DistributeCountersEffect extends OneShotEffect { private final CounterType counterType; private final DynamicValue amount; - private final boolean removeAtEndOfTurn; + private boolean removeAtEndOfTurn = false; private final String targetDescription; + /** + * Distribute +1/+1 counters among targets + */ + public DistributeCountersEffect(int amount, String targetDescription) { + this(CounterType.P1P1, StaticValue.get(amount), targetDescription); + } + public DistributeCountersEffect(CounterType counterType, int amount, String targetDescription) { - this(counterType, amount, false, targetDescription); + this(counterType, StaticValue.get(amount), targetDescription); } - public DistributeCountersEffect(CounterType counterType, int amount, boolean removeAtEndOfTurn, String targetDescription) { - this(counterType, StaticValue.get(amount), removeAtEndOfTurn, targetDescription); - } - - public DistributeCountersEffect(CounterType counterType, DynamicValue amount, boolean removeAtEndOfTurn, String targetDescription) { + public DistributeCountersEffect(CounterType counterType, DynamicValue amount, String targetDescription) { super(Outcome.BoostCreature); this.counterType = counterType; this.amount = amount; - this.removeAtEndOfTurn = removeAtEndOfTurn; this.targetDescription = targetDescription; } @@ -56,6 +57,11 @@ public class DistributeCountersEffect extends OneShotEffect { return new DistributeCountersEffect(this); } + public DistributeCountersEffect withRemoveAtEndOfTurn() { + this.removeAtEndOfTurn = true; + return this; + } + @Override public boolean apply(Game game, Ability source) { if (!source.getTargets().isEmpty()) { @@ -99,7 +105,7 @@ class RemoveCountersAtEndOfTurn extends OneShotEffect { private final CounterType counterType; - public RemoveCountersAtEndOfTurn(CounterType counterType) { + RemoveCountersAtEndOfTurn(CounterType counterType) { super(Outcome.Detriment); this.counterType = counterType; String name = counterType.getName(); -- 2.47.2 From 682a9d1aa6109da50a8e6f29d8246d2a62d78588 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sat, 30 Nov 2024 15:44:58 -0500 Subject: [PATCH 28/40] refactor: remove unused class and constructors --- .../main/java/mage/target/TargetAmount.java | 9 +--- .../common/TargetCreatureOrPlayerAmount.java | 46 ------------------- .../common/TargetPermanentOrPlayerAmount.java | 4 -- 3 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 Mage/src/main/java/mage/target/common/TargetCreatureOrPlayerAmount.java diff --git a/Mage/src/main/java/mage/target/TargetAmount.java b/Mage/src/main/java/mage/target/TargetAmount.java index 76ac8c7186f..6bd1e51c510 100644 --- a/Mage/src/main/java/mage/target/TargetAmount.java +++ b/Mage/src/main/java/mage/target/TargetAmount.java @@ -2,7 +2,6 @@ package mage.target; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.StaticValue; import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; @@ -19,14 +18,8 @@ public abstract class TargetAmount extends TargetImpl { DynamicValue amount; int remainingAmount; - public TargetAmount(int amount) { - this(StaticValue.get(amount)); - } - - public TargetAmount(DynamicValue amount) { + protected TargetAmount(DynamicValue amount) { this.amount = amount; - //this.remainingAmount = amount; - amountWasSet = false; } protected TargetAmount(final TargetAmount target) { diff --git a/Mage/src/main/java/mage/target/common/TargetCreatureOrPlayerAmount.java b/Mage/src/main/java/mage/target/common/TargetCreatureOrPlayerAmount.java deleted file mode 100644 index 11cb59db031..00000000000 --- a/Mage/src/main/java/mage/target/common/TargetCreatureOrPlayerAmount.java +++ /dev/null @@ -1,46 +0,0 @@ -package mage.target.common; - -import mage.abilities.dynamicvalue.DynamicValue; -import mage.abilities.dynamicvalue.common.StaticValue; -import mage.constants.CardType; -import mage.constants.Zone; -import mage.filter.common.FilterPermanentOrPlayer; - -/** - * @author BetaSteward_at_googlemail.com - */ -public class TargetCreatureOrPlayerAmount extends TargetPermanentOrPlayerAmount { - - private static final FilterPermanentOrPlayer defaultFilter - = new FilterPermanentOrPlayer("creatures and/or players"); - - static { - defaultFilter.getPermanentFilter().add(CardType.CREATURE.getPredicate()); - } - - public TargetCreatureOrPlayerAmount(int amount) { - // 107.1c If a rule or ability instructs a player to choose “any number,” that player may choose - // any positive number or zero, unless something (such as damage or counters) is being divided - // or distributed among “any number” of players and/or objects. In that case, a nonzero number - // of players and/or objects must be chosen if possible. - this(StaticValue.get(amount)); - this.minNumberOfTargets = 1; - } - - public TargetCreatureOrPlayerAmount(DynamicValue amount) { - super(amount); - this.zone = Zone.ALL; - this.filter = defaultFilter; - this.targetName = filter.getMessage(); - } - - private TargetCreatureOrPlayerAmount(final TargetCreatureOrPlayerAmount target) { - super(target); - this.filter = target.filter.copy(); - } - - @Override - public TargetCreatureOrPlayerAmount copy() { - return new TargetCreatureOrPlayerAmount(this); - } -} diff --git a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java index 63b3799d33a..9e42eb94fb6 100644 --- a/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java +++ b/Mage/src/main/java/mage/target/common/TargetPermanentOrPlayerAmount.java @@ -22,10 +22,6 @@ public abstract class TargetPermanentOrPlayerAmount extends TargetAmount { protected FilterPermanentOrPlayer filter; - TargetPermanentOrPlayerAmount(DynamicValue amount) { - this(amount, 0); - } - TargetPermanentOrPlayerAmount(DynamicValue amount, int maxNumberOfTargets) { super(amount); this.maxNumberOfTargets = maxNumberOfTargets; -- 2.47.2 From 12808184b737f07238903574fd09a8b18247381b Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Sat, 30 Nov 2024 23:13:17 +0000 Subject: [PATCH 29/40] [MB2] Implement Wisedrafter's Will --- .../src/mage/cards/w/WisedraftersWill.java | 50 +++++++++++++++++++ Mage.Sets/src/mage/sets/MysteryBooster2.java | 1 + 2 files changed, 51 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/w/WisedraftersWill.java diff --git a/Mage.Sets/src/mage/cards/w/WisedraftersWill.java b/Mage.Sets/src/mage/cards/w/WisedraftersWill.java new file mode 100644 index 00000000000..6286cc9f575 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WisedraftersWill.java @@ -0,0 +1,50 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.PlayWithHandRevealedEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.target.TargetSpell; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class WisedraftersWill extends CardImpl { + + public WisedraftersWill(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}"); + + // Your opponents play with their hands revealed. + this.addAbility(new SimpleStaticAbility(new PlayWithHandRevealedEffect(TargetController.OPPONENT))); + + // {U}, Sacrifice Wisedrafter's Will: Draw a card. + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new ManaCostsImpl<>("{U}")); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + + // {U}{U}, Sacrifice Wisedrafter's Will: Counter target spell. + ability = new SimpleActivatedAbility(new CounterTargetEffect(), new ManaCostsImpl<>("{U}{U}")); + ability.addCost(new SacrificeSourceCost()); + ability.addTarget(new TargetSpell()); + this.addAbility(ability); + } + + private WisedraftersWill(final WisedraftersWill card) { + super(card); + } + + @Override + public WisedraftersWill copy() { + return new WisedraftersWill(this); + } +} diff --git a/Mage.Sets/src/mage/sets/MysteryBooster2.java b/Mage.Sets/src/mage/sets/MysteryBooster2.java index 74fcfd4e4e4..78e6cddad8e 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster2.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster2.java @@ -277,6 +277,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Wasteland", 115, Rarity.UNCOMMON, mage.cards.w.Wasteland.class)); cards.add(new SetCardInfo("Whiteout", 77, Rarity.COMMON, mage.cards.w.Whiteout.class)); cards.add(new SetCardInfo("Winds of Change", 201, Rarity.UNCOMMON, mage.cards.w.WindsOfChange.class)); + cards.add(new SetCardInfo("Wisedrafter's Will", 303, Rarity.RARE, mage.cards.w.WisedraftersWill.class)); cards.add(new SetCardInfo("Wish", 64, Rarity.RARE, mage.cards.w.Wish.class)); cards.add(new SetCardInfo("Wishclaw Talisman", 51, Rarity.RARE, mage.cards.w.WishclawTalisman.class)); cards.add(new SetCardInfo("Worst Fears", 52, Rarity.MYTHIC, mage.cards.w.WorstFears.class)); -- 2.47.2 From b70728b42a7977fde543e181062256cbb67f8478 Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Sun, 1 Dec 2024 00:32:39 +0000 Subject: [PATCH 30/40] [MB2] Implement Indicate --- Mage.Sets/src/mage/cards/i/Indicate.java | 32 ++++++++++++++++++++ Mage.Sets/src/mage/sets/MysteryBooster2.java | 1 + 2 files changed, 33 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/i/Indicate.java diff --git a/Mage.Sets/src/mage/cards/i/Indicate.java b/Mage.Sets/src/mage/cards/i/Indicate.java new file mode 100644 index 00000000000..40da8d0ec0c --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/Indicate.java @@ -0,0 +1,32 @@ +package mage.cards.i; + +import mage.abilities.effects.common.InfoEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class Indicate extends CardImpl { + + public Indicate(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{0}"); + + // Target permanent. + this.getSpellAbility().addEffect(new InfoEffect("Target permanent.")); + this.getSpellAbility().addTarget(new TargetPermanent()); + } + + private Indicate(final Indicate card) { + super(card); + } + + @Override + public Indicate copy() { + return new Indicate(this); + } +} diff --git a/Mage.Sets/src/mage/sets/MysteryBooster2.java b/Mage.Sets/src/mage/sets/MysteryBooster2.java index 78e6cddad8e..b1b39cbb913 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster2.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster2.java @@ -126,6 +126,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Hunting Cheetah", 134, Rarity.COMMON, mage.cards.h.HuntingCheetah.class)); cards.add(new SetCardInfo("Hydroblast", 165, Rarity.COMMON, mage.cards.h.Hydroblast.class)); cards.add(new SetCardInfo("Ice-Fang Coatl", 83, Rarity.RARE, mage.cards.i.IceFangCoatl.class)); + cards.add(new SetCardInfo("Indicate", 265, Rarity.RARE, mage.cards.i.Indicate.class)); cards.add(new SetCardInfo("Iona, Shield of Emeria", 12, Rarity.MYTHIC, mage.cards.i.IonaShieldOfEmeria.class)); cards.add(new SetCardInfo("Isochron Scepter", 96, Rarity.UNCOMMON, mage.cards.i.IsochronScepter.class)); cards.add(new SetCardInfo("Jace Beleren", 29, Rarity.MYTHIC, mage.cards.j.JaceBeleren.class)); -- 2.47.2 From 5ab7d3c115322a0f968e72123484244a90f82921 Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Sun, 1 Dec 2024 00:33:25 +0000 Subject: [PATCH 31/40] [MB2] Implement Gobland --- Mage.Sets/src/mage/cards/g/Gobland.java | 48 ++++++++++++++++++++ Mage.Sets/src/mage/sets/MysteryBooster2.java | 1 + 2 files changed, 49 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/Gobland.java diff --git a/Mage.Sets/src/mage/cards/g/Gobland.java b/Mage.Sets/src/mage/cards/g/Gobland.java new file mode 100644 index 00000000000..b4a572b2f87 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/Gobland.java @@ -0,0 +1,48 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.CantBlockAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.mana.RedManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class Gobland extends CardImpl { + + public Gobland(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND, CardType.CREATURE}, ""); + this.subtype.add(SubType.MOUNTAIN, SubType.GOBLIN); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + this.color.setRed(true); + + // (Gobland isn't a spell, it's affected by summoning sickness, and it has "{T}: Add {R}.") + this.addAbility(new SimpleStaticAbility(new InfoEffect( + "({this} isn't a spell, it's affected by summoning sickness, and it has \"{T}: Add {R}.\")" + ))); + + // Add {R} + this.addAbility(new RedManaAbility()); + + // Gobland can't block. + this.addAbility(new CantBlockAbility()); + } + + private Gobland(final Gobland card) { + super(card); + } + + @Override + public Gobland copy() { + return new Gobland(this); + } +} diff --git a/Mage.Sets/src/mage/sets/MysteryBooster2.java b/Mage.Sets/src/mage/sets/MysteryBooster2.java index b1b39cbb913..06da5dcf66e 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster2.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster2.java @@ -107,6 +107,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Gitaxian Probe", 28, Rarity.COMMON, mage.cards.g.GitaxianProbe.class)); cards.add(new SetCardInfo("Giver of Runes", 147, Rarity.RARE, mage.cards.g.GiverOfRunes.class)); cards.add(new SetCardInfo("Gix, Yawgmoth Praetor", 245, Rarity.MYTHIC, mage.cards.g.GixYawgmothPraetor.class)); + cards.add(new SetCardInfo("Gobland", 374, Rarity.RARE, mage.cards.g.Gobland.class)); cards.add(new SetCardInfo("Goblin Charbelcher", 221, Rarity.RARE, mage.cards.g.GoblinCharbelcher.class)); cards.add(new SetCardInfo("Goblin Gang Leader", 144, Rarity.UNCOMMON, mage.cards.g.GoblinGangLeader.class)); cards.add(new SetCardInfo("Goblin Guide", 58, Rarity.RARE, mage.cards.g.GoblinGuide.class)); -- 2.47.2 From 1d5bb57c63af6967d9b70ca3b06459e70dcf9a8e Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Sun, 1 Dec 2024 00:33:49 +0000 Subject: [PATCH 32/40] [MB2] Implement Mox Poison --- Mage.Sets/src/mage/cards/m/MoxPoison.java | 38 ++++++++++++++++++++ Mage.Sets/src/mage/sets/MysteryBooster2.java | 1 + 2 files changed, 39 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MoxPoison.java diff --git a/Mage.Sets/src/mage/cards/m/MoxPoison.java b/Mage.Sets/src/mage/cards/m/MoxPoison.java new file mode 100644 index 00000000000..8955f0120a8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MoxPoison.java @@ -0,0 +1,38 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.effects.common.counter.AddCountersPlayersEffect; +import mage.abilities.mana.AnyColorManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.counters.CounterType; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class MoxPoison extends CardImpl { + + public MoxPoison(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{0}"); + + // {T}: Add one mana of any color. You get two poison counters. + Ability ability = new AnyColorManaAbility(); + ability.addEffect(new AddCountersPlayersEffect( + CounterType.POISON.createInstance(2), TargetController.YOU + )); + this.addAbility(ability); + } + + private MoxPoison(final MoxPoison card) { + super(card); + } + + @Override + public MoxPoison copy() { + return new MoxPoison(this); + } +} diff --git a/Mage.Sets/src/mage/sets/MysteryBooster2.java b/Mage.Sets/src/mage/sets/MysteryBooster2.java index 06da5dcf66e..37fb0d399e7 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster2.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster2.java @@ -162,6 +162,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Mirri's Guile", 209, Rarity.RARE, mage.cards.m.MirrisGuile.class)); cards.add(new SetCardInfo("Mirri, Weatherlight Duelist", 252, Rarity.MYTHIC, mage.cards.m.MirriWeatherlightDuelist.class)); cards.add(new SetCardInfo("Mishra's Bauble", 97, Rarity.UNCOMMON, mage.cards.m.MishrasBauble.class)); + cards.add(new SetCardInfo("Mox Poison", 369, Rarity.RARE, mage.cards.m.MoxPoison.class)); cards.add(new SetCardInfo("Multani, Yavimaya's Avatar", 248, Rarity.MYTHIC, mage.cards.m.MultaniYavimayasAvatar.class)); cards.add(new SetCardInfo("Mutilate", 45, Rarity.RARE, mage.cards.m.Mutilate.class)); cards.add(new SetCardInfo("Mystic Sanctuary", 110, Rarity.COMMON, mage.cards.m.MysticSanctuary.class)); -- 2.47.2 From 00a002b97ef14889243859a2e0b31652dcb2e34a Mon Sep 17 00:00:00 2001 From: PurpleCrowbar <26198472+PurpleCrowbar@users.noreply.github.com> Date: Sun, 1 Dec 2024 00:34:21 +0000 Subject: [PATCH 33/40] [MB2] Implement Wrath of Leknif --- Mage.Sets/src/mage/cards/w/WrathOfLeknif.java | 33 +++++++++++++++++++ Mage.Sets/src/mage/sets/MysteryBooster2.java | 1 + 2 files changed, 34 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/w/WrathOfLeknif.java diff --git a/Mage.Sets/src/mage/cards/w/WrathOfLeknif.java b/Mage.Sets/src/mage/cards/w/WrathOfLeknif.java new file mode 100644 index 00000000000..f91fb8161bf --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WrathOfLeknif.java @@ -0,0 +1,33 @@ +package mage.cards.w; + +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.UntapLandsEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author PurpleCrowbar + */ +public final class WrathOfLeknif extends CardImpl { + + public WrathOfLeknif(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}{W}{U}"); + + // Destroy all creatures. They can't be regenerated. Untap up to four lands you control. + this.getSpellAbility().addEffect(new DestroyAllEffect(StaticFilters.FILTER_PERMANENT_CREATURES, true)); + this.getSpellAbility().addEffect(new UntapLandsEffect(4, true, true)); + } + + private WrathOfLeknif(final WrathOfLeknif card) { + super(card); + } + + @Override + public WrathOfLeknif copy() { + return new WrathOfLeknif(this); + } +} diff --git a/Mage.Sets/src/mage/sets/MysteryBooster2.java b/Mage.Sets/src/mage/sets/MysteryBooster2.java index 37fb0d399e7..3a97c5f3c87 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster2.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster2.java @@ -285,6 +285,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Wishclaw Talisman", 51, Rarity.RARE, mage.cards.w.WishclawTalisman.class)); cards.add(new SetCardInfo("Worst Fears", 52, Rarity.MYTHIC, mage.cards.w.WorstFears.class)); cards.add(new SetCardInfo("Wowzer, the Aspirational", 365, Rarity.RARE, mage.cards.w.WowzerTheAspirational.class)); + cards.add(new SetCardInfo("Wrath of Leknif", 366, Rarity.RARE, mage.cards.w.WrathOfLeknif.class)); cards.add(new SetCardInfo("Xantcha, Sleeper Agent", 253, Rarity.RARE, mage.cards.x.XantchaSleeperAgent.class)); cards.add(new SetCardInfo("Yorion, Sky Nomad", 94, Rarity.RARE, mage.cards.y.YorionSkyNomad.class)); cards.add(new SetCardInfo("Zombie Master", 188, Rarity.RARE, mage.cards.z.ZombieMaster.class)); -- 2.47.2 From efe04f69b043897e819c809c67a270edcd3bac79 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sat, 30 Nov 2024 20:23:33 -0500 Subject: [PATCH 34/40] implement [DSK] The Tale of Tamiyo (closes #13086) --- .../src/mage/cards/t/TheTaleOfTamiyo.java | 186 ++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 187 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TheTaleOfTamiyo.java diff --git a/Mage.Sets/src/mage/cards/t/TheTaleOfTamiyo.java b/Mage.Sets/src/mage/cards/t/TheTaleOfTamiyo.java new file mode 100644 index 00000000000..af41a7f1be3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheTaleOfTamiyo.java @@ -0,0 +1,186 @@ +package mage.cards.t; + +import mage.ApprovingObject; +import mage.abilities.Ability; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.*; + +/** + * @author frafen, xenohedron + */ +public final class TheTaleOfTamiyo extends CardImpl { + + private static final FilterCard filter = new FilterCard("instant, sorcery, and/or Tamiyo planeswalker cards from your graveyard"); + + static { + filter.add(Predicates.or( + CardType.INSTANT.getPredicate(), + CardType.SORCERY.getPredicate(), + SubType.TAMIYO.getPredicate() + )); + } + + public TheTaleOfTamiyo(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I, II, III -- Mill two cards. If two cards that share a card type were milled this way, draw a card and repeat this process. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III, + new TheTaleOfTamiyoEffect1() + ); + + // IV -- Exile any number of target instant, sorcery, and/or Tamiyo planeswalker cards from your graveyard. Copy them. You may cast any number of the copies. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_IV, + new TheTaleOfTamiyoEffect2(), + new TargetCardInYourGraveyard(0, Integer.MAX_VALUE, filter) + ); + + this.addAbility(sagaAbility); + } + + private TheTaleOfTamiyo(final TheTaleOfTamiyo card) { + super(card); + } + + @Override + public TheTaleOfTamiyo copy() { + return new TheTaleOfTamiyo(this); + } +} + +// Based on Grindstone +class TheTaleOfTamiyoEffect1 extends OneShotEffect { + + TheTaleOfTamiyoEffect1() { + super(Outcome.DrawCard); + this.staticText = "Mill two cards. If two cards that share a card type were milled this way, draw a card and repeat this process."; + } + + private TheTaleOfTamiyoEffect1(final TheTaleOfTamiyoEffect1 effect) { + super(effect); + } + + @Override + public TheTaleOfTamiyoEffect1 copy() { + return new TheTaleOfTamiyoEffect1(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + + // In case of a replacement effect that would cause an infinite loop on repeating the process + int possibleIterations = controller.getLibrary().size() / 2; + int iteration = 0; + + do { + iteration++; + if (iteration > possibleIterations + 20) { + // Protection against infinity loops + game.setDraw(source.getControllerId()); + return true; + } + + // Mill 2 cards + List cards = new ArrayList<>(controller + .millCards(2, source, game) + .getCards(game)); + + // Stop if less than 2 cards were milled + if (cards.size() < 2) { + break; + } + + // Set to hold types of the first card + Set firstCardTypes = new HashSet<>(cards.get(0).getCardType()); + + // Check if there's at least one type in common + boolean typesMatch = false; + + // Iterate over the second card's types to see if there is any overlap with the first card's types + for (CardType type : cards.get(1).getCardType()) { + if (firstCardTypes.contains(type)) { + typesMatch = true; + break; + } + } + + // If there is a match, draw a card and continue; otherwise, break + if (typesMatch) { + controller.drawCards(1, source, game); + } else { + break; + } + + } while (controller.canRespond()); + + return true; + } + + +} + +class TheTaleOfTamiyoEffect2 extends OneShotEffect { + + TheTaleOfTamiyoEffect2() { + super(Outcome.PlayForFree); + this.staticText = "Exile any number of target instant, sorcery, and/or Tamiyo planeswalker cards " + + "from your graveyard. Copy them. You may cast any number of the copies."; + } + + private TheTaleOfTamiyoEffect2(final TheTaleOfTamiyoEffect2 effect) { + super(effect); + } + + @Override + public TheTaleOfTamiyoEffect2 copy() { + return new TheTaleOfTamiyoEffect2(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Cards toExile = new CardsImpl(); + getTargetPointer().getTargets(game, source) + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .forEach(toExile::add); + if (controller == null || toExile.isEmpty()) { + return false; + } + controller.moveCards(toExile, Zone.EXILED, source, game); + toExile.retainZone(Zone.EXILED, game); + for (Card card : toExile.getCards(game)) { + if (controller.chooseUse(outcome, "Cast copy of " + card.getName() + "?", source, game)) { + Card cardCopy = game.copyCard(card, source, source.getControllerId()); + game.getState().setValue("PlayFromNotOwnHandZone" + cardCopy.getId(), Boolean.TRUE); + controller.cast( + controller.chooseAbilityForCast(cardCopy, game, false), + game, false, new ApprovingObject(source, game) + ); + game.getState().setValue("PlayFromNotOwnHandZone" + cardCopy.getId(), null); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 0cb39c40099..28e8282694e 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -223,6 +223,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("The Rollercrusher Ride", 298, Rarity.MYTHIC, mage.cards.t.TheRollercrusherRide.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Rollercrusher Ride", 317, Rarity.MYTHIC, mage.cards.t.TheRollercrusherRide.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Swarmweaver", 236, Rarity.RARE, mage.cards.t.TheSwarmweaver.class)); + cards.add(new SetCardInfo("The Tale of Tamiyo", 75, Rarity.RARE, mage.cards.t.TheTaleOfTamiyo.class)); cards.add(new SetCardInfo("The Wandering Rescuer", 41, Rarity.MYTHIC, mage.cards.t.TheWanderingRescuer.class)); cards.add(new SetCardInfo("Thornspire Verge", 270, Rarity.RARE, mage.cards.t.ThornspireVerge.class)); cards.add(new SetCardInfo("Threats Around Every Corner", 200, Rarity.UNCOMMON, mage.cards.t.ThreatsAroundEveryCorner.class)); -- 2.47.2 From f06a4c772b937546c816a2b86926631ee562a0c5 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sat, 30 Nov 2024 20:25:47 -0500 Subject: [PATCH 35/40] fix MB2 playtest card legality (resolves #13090) --- Mage/src/main/java/mage/constants/SetType.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/constants/SetType.java b/Mage/src/main/java/mage/constants/SetType.java index c4a68115e75..cf426394a5e 100644 --- a/Mage/src/main/java/mage/constants/SetType.java +++ b/Mage/src/main/java/mage/constants/SetType.java @@ -37,7 +37,10 @@ public enum SetType { public boolean isEternalLegal() { // any official sets except un-sets - return this != SetType.CUSTOM_SET && this != SetType.JOKE_SET && this != SetType.MAGIC_ARENA; + return this != SetType.CUSTOM_SET + && this != SetType.JOKE_SET + && this != SetType.REMIX // to exclude MB2 playtest cards + && this != SetType.MAGIC_ARENA; } public boolean isStandardLegal() { -- 2.47.2 From e3caf6ce4380b532eb37aabde13c7411ef43b7a2 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sat, 30 Nov 2024 20:54:11 -0500 Subject: [PATCH 36/40] implement [LEG] Firestorm Phoenix (closes #12990) --- .../src/mage/cards/f/FirestormPhoenix.java | 201 ++++++++++++++++++ Mage.Sets/src/mage/sets/Legends.java | 1 + .../src/mage/sets/MastersEditionIII.java | 3 +- 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/f/FirestormPhoenix.java diff --git a/Mage.Sets/src/mage/cards/f/FirestormPhoenix.java b/Mage.Sets/src/mage/cards/f/FirestormPhoenix.java new file mode 100644 index 00000000000..18608d7ade1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FirestormPhoenix.java @@ -0,0 +1,201 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author RobertFrosty, xenohedron + */ + +public final class FirestormPhoenix extends CardImpl { + + public FirestormPhoenix(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}"); + + this.subtype.add(SubType.PHOENIX); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // If Firestorm Phoenix would die, return Firestorm Phoenix to its owner's hand instead. + // Until that player's next turn, that player plays with that card revealed in their hand and can't play it. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new FirestormPhoenixEffect())); + } + + private FirestormPhoenix(final FirestormPhoenix card) { + super(card); + } + + @Override + public FirestormPhoenix copy() { + return new FirestormPhoenix(this); + } +} + +class FirestormPhoenixEffect extends ReplacementEffectImpl { + + + FirestormPhoenixEffect() { + super(Duration.Custom, Outcome.ReturnToHand); + staticText = "If {this} would die, return {this} to its owner's hand instead. " + + "Until that player's next turn, that player plays with that card revealed in their hand and can't play it"; + } + + private FirestormPhoenixEffect(final FirestormPhoenixEffect effect) { + super(effect); + } + + @Override + public FirestormPhoenixEffect copy() { + return new FirestormPhoenixEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent permanent = ((ZoneChangeEvent) event).getTarget(); + if (controller == null || permanent == null) { + return false; + } + controller.moveCards(permanent, Zone.HAND, source, game); + Card card = game.getCard(permanent.getId()); + if (card == null) { + return true; + } + game.addEffect(new FirestormPhoenixRevealEffect().setTargetPointer(new FixedTarget(card, game)), source); + game.addEffect(new FirestormPhoenixRestrictEffect().setTargetPointer(new FixedTarget(card, game)), source); + return true; + + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.getSourceId().equals(event.getTargetId()) + && ((ZoneChangeEvent) event).isDiesEvent(); + } +} + +class FirestormPhoenixRevealEffect extends ContinuousEffectImpl { + + private int startingTurnNum; + + FirestormPhoenixRevealEffect() { + super(Duration.Custom, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment); + } + + private FirestormPhoenixRevealEffect(final FirestormPhoenixRevealEffect effect) { + super(effect); + this.startingTurnNum = effect.startingTurnNum; + } + + @Override + public FirestormPhoenixRevealEffect copy() { + return new FirestormPhoenixRevealEffect(this); + } + + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + startingTurnNum = game.getTurnNum(); + } + + @Override + public boolean isInactive(Ability source, Game game) { + if (game.getCard(getTargetPointer().getFirst(game, source)) == null) { + return true; + } + return game.isActivePlayer(game.getOwnerId(getTargetPointer().getFirst(game, source))) + && game.getTurnNum() > startingTurnNum; + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null) { + discard(); + return false; + } + Player player = game.getPlayer(card.getOwnerId()); + if (player != null) { + player.revealCards(card.getIdName(), new CardsImpl(card), game, false); + } + return true; + } +} + +class FirestormPhoenixRestrictEffect extends ContinuousRuleModifyingEffectImpl { + + private int startingTurnNum; + + FirestormPhoenixRestrictEffect() { + super(Duration.Custom, Outcome.Detriment); + } + + private FirestormPhoenixRestrictEffect(final FirestormPhoenixRestrictEffect effect) { + super(effect); + this.startingTurnNum = effect.startingTurnNum; + } + + @Override + public FirestormPhoenixRestrictEffect copy() { + return new FirestormPhoenixRestrictEffect(this); + } + + @Override + public void init(Ability source, Game game) { + super.init(source, game); + startingTurnNum = game.getTurnNum(); + } + + @Override + public boolean isInactive(Ability source, Game game) { + if (game.getCard(getTargetPointer().getFirst(game, source)) == null) { + return true; + } + return game.isActivePlayer(game.getOwnerId(getTargetPointer().getFirst(game, source))) + && game.getTurnNum() > startingTurnNum; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CAST_SPELL_LATE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Card cardInHand = game.getCard(getTargetPointer().getFirst(game, source)); + SpellAbility spellAbility = SpellAbility.getSpellAbilityFromEvent(event, game); + if (cardInHand == null || spellAbility == null) { + return false; + } + Card cardToCast = spellAbility.getCharacteristics(game); + return cardToCast != null && cardToCast.getId().equals(cardInHand.getId()); + } +} diff --git a/Mage.Sets/src/mage/sets/Legends.java b/Mage.Sets/src/mage/sets/Legends.java index 2800ea8176c..e3a1b69ff3a 100644 --- a/Mage.Sets/src/mage/sets/Legends.java +++ b/Mage.Sets/src/mage/sets/Legends.java @@ -107,6 +107,7 @@ public final class Legends extends ExpansionSet { cards.add(new SetCardInfo("Feint", 146, Rarity.COMMON, mage.cards.f.Feint.class)); cards.add(new SetCardInfo("Field of Dreams", 55, Rarity.RARE, mage.cards.f.FieldOfDreams.class)); cards.add(new SetCardInfo("Fire Sprites", 186, Rarity.COMMON, mage.cards.f.FireSprites.class)); + cards.add(new SetCardInfo("Firestorm Phoenix", 145, Rarity.RARE, mage.cards.f.FirestormPhoenix.class)); cards.add(new SetCardInfo("Flash Counter", 56, Rarity.COMMON, mage.cards.f.FlashCounter.class)); cards.add(new SetCardInfo("Flash Flood", 57, Rarity.COMMON, mage.cards.f.FlashFlood.class)); cards.add(new SetCardInfo("Floral Spuzzem", 187, Rarity.UNCOMMON, mage.cards.f.FloralSpuzzem.class)); diff --git a/Mage.Sets/src/mage/sets/MastersEditionIII.java b/Mage.Sets/src/mage/sets/MastersEditionIII.java index 481db42e98c..32060c77db3 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionIII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionIII.java @@ -90,6 +90,7 @@ public final class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Fire Ambush", 97, Rarity.COMMON, mage.cards.f.FireAmbush.class)); cards.add(new SetCardInfo("Fire Drake", 98, Rarity.COMMON, mage.cards.f.FireDrake.class)); cards.add(new SetCardInfo("Fire Sprites", 118, Rarity.COMMON, mage.cards.f.FireSprites.class)); + cards.add(new SetCardInfo("Firestorm Phoenix", 145, Rarity.RARE, mage.cards.f.FirestormPhoenix.class)); cards.add(new SetCardInfo("Flash Flood", 35, Rarity.UNCOMMON, mage.cards.f.FlashFlood.class)); cards.add(new SetCardInfo("Forced Retreat", 37, Rarity.COMMON, mage.cards.f.ForcedRetreat.class)); cards.add(new SetCardInfo("Force Spike", 36, Rarity.COMMON, mage.cards.f.ForceSpike.class)); @@ -254,4 +255,4 @@ public final class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Zhang Fei, Fierce Warrior", 28, Rarity.UNCOMMON, mage.cards.z.ZhangFeiFierceWarrior.class)); cards.add(new SetCardInfo("Zodiac Dragon", 112, Rarity.RARE, mage.cards.z.ZodiacDragon.class)); } -} \ No newline at end of file +} -- 2.47.2 From 66b4bae2c34ccc7988487ececa99506f93d6abb6 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sat, 30 Nov 2024 22:38:03 -0500 Subject: [PATCH 37/40] fix typo --- Mage.Sets/src/mage/sets/MastersEditionIII.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/MastersEditionIII.java b/Mage.Sets/src/mage/sets/MastersEditionIII.java index 32060c77db3..d2cfe1f52b1 100644 --- a/Mage.Sets/src/mage/sets/MastersEditionIII.java +++ b/Mage.Sets/src/mage/sets/MastersEditionIII.java @@ -90,7 +90,7 @@ public final class MastersEditionIII extends ExpansionSet { cards.add(new SetCardInfo("Fire Ambush", 97, Rarity.COMMON, mage.cards.f.FireAmbush.class)); cards.add(new SetCardInfo("Fire Drake", 98, Rarity.COMMON, mage.cards.f.FireDrake.class)); cards.add(new SetCardInfo("Fire Sprites", 118, Rarity.COMMON, mage.cards.f.FireSprites.class)); - cards.add(new SetCardInfo("Firestorm Phoenix", 145, Rarity.RARE, mage.cards.f.FirestormPhoenix.class)); + cards.add(new SetCardInfo("Firestorm Phoenix", 99, Rarity.RARE, mage.cards.f.FirestormPhoenix.class)); cards.add(new SetCardInfo("Flash Flood", 35, Rarity.UNCOMMON, mage.cards.f.FlashFlood.class)); cards.add(new SetCardInfo("Forced Retreat", 37, Rarity.COMMON, mage.cards.f.ForcedRetreat.class)); cards.add(new SetCardInfo("Force Spike", 36, Rarity.COMMON, mage.cards.f.ForceSpike.class)); -- 2.47.2 From 02e4df7fa9547e7901f4c97b032abcee50dd3ef6 Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Sat, 30 Nov 2024 22:48:31 -0500 Subject: [PATCH 38/40] [FDN] Fix Alesha, Who Laughs at Fate predicate correctly. Fixes #13092 --- Mage.Sets/src/mage/cards/a/AleshaWhoLaughsAtFate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AleshaWhoLaughsAtFate.java b/Mage.Sets/src/mage/cards/a/AleshaWhoLaughsAtFate.java index 8a75217b72f..5371fcbf667 100644 --- a/Mage.Sets/src/mage/cards/a/AleshaWhoLaughsAtFate.java +++ b/Mage.Sets/src/mage/cards/a/AleshaWhoLaughsAtFate.java @@ -82,8 +82,8 @@ enum AleshaWhoLaughsAtFatePredicate implements ObjectSourcePlayerPredicate public boolean apply(ObjectSourcePlayer input, Game game) { return Optional .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) - .map(MageObject::getManaValue) - .map(p -> input.getObject().getManaValue() <= p) + .map(MageObject::getPower) + .map(p -> input.getObject().getManaValue() <= p.getValue()) .orElse(false); } } -- 2.47.2 From c6bec887b9fd99a1d9db43308a04183054dbdd56 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 1 Dec 2024 15:28:09 +0400 Subject: [PATCH 39/40] Triggered abilities - fixed that it can trigger from hand or library (related to #13089, regression from #13088) --- .../single/mh2/DauthiVoidwalkerTest.java | 24 ++++++ .../main/java/mage/abilities/AbilityImpl.java | 83 +++++++++++-------- 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java index 7c81f698159..4f7b3d4d225 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh2/DauthiVoidwalkerTest.java @@ -73,4 +73,28 @@ public class DauthiVoidwalkerTest extends CardTestPlayerBase { assertLife(playerA, 20); assertLife(playerB, 20 - 3); } + + @Test + public void test_MakeSureNoTriggerInWrongZones() { + // bug report: it triggered in library + // https://github.com/magefree/mage/issues/13089 + + // If a card would be put into an opponent's graveyard from anywhere, instead exile it with a void counter on it. + // {T}, Sacrifice Dauthi Voidwalker: Choose an exiled card an opponent owns with a void counter on it. You may play it this turn without paying its mana cost. + addCard(Zone.HAND, playerA, "Dauthi Voidwalker", 1); + // + addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1); + // + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + // kill B's creature without triggers + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Balduvian Bears"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkGraveyardCount("after kill", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Balduvian Bears", 1); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } } diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 214b7b015a9..242a25f863e 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -1182,22 +1182,21 @@ public abstract class AbilityImpl implements Ability { // workaround for singleton abilities like Flying UUID affectedSourceId = getRealSourceObjectId(this, sourceObject); + MageObject affectedSourceObject = game.getObject(affectedSourceId); - // in command zone + // global game effects (works all the time and don't have sourceId, example: FinalityCounterEffect) + if (affectedSourceId == null) { + return true; + } + + // emblems/dungeons/planes effects (works all the time, store in command zone) if (zone == Zone.COMMAND) { - if (affectedSourceId == null) { - // commander effects + if (affectedSourceObject instanceof Emblem || affectedSourceObject instanceof Dungeon || affectedSourceObject instanceof Plane) { 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; - } } } - // on entering permanents - must use static abilities like it already on battlefield + // on entering permanents must use static abilities like it already on battlefield // example: Tatterkite enters without counters from Mikaeus, the Unhallowed if (game.getPermanentEntering(affectedSourceId) != null && zone == Zone.BATTLEFIELD) { return true; @@ -1208,7 +1207,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(affectedSourceId); + Zone affectedObjectZone = game.getState().getZone(affectedSourceId); // 603.10. // ... @@ -1220,32 +1219,50 @@ public abstract class AbilityImpl implements Ability { // Some zone-change triggers look back in time. These are leaves-the-battlefield abilities, // abilities that trigger when a card leaves a graveyard, and abilities that trigger when an object that all // players can see is put into a hand or library. - // TODO: research "leaves a graveyard" - // TODO: research "put into a hand or library" - if (isTriggerCanFireAfterLeaveBattlefield(event)) { - // permanents with normal triggers - if (sourceObject instanceof Permanent) { // TODO: use affectedSourceObject here? - // support leaves-the-battlefield abilities - sourceObjectZone = Zone.BATTLEFIELD; - } - // permanents with continues effects like Yixlid Jailer, see related code "isInUseableZone(game, null" - if (sourceObject == null && this instanceof StaticAbility) { - sourceObjectZone = Zone.BATTLEFIELD; + + // TODO: research use cases and implement shared logic with "looking zone" instead LKI only + // in most use cases it's already supported by event (example: saved permanent object in event's target) + // [x] 603.10a leaves-the-battlefield abilities and other + // [ ] 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. + + if (event == null) { + // state base triggers - use only actual state + } else { + // event triggers and continues effects - can look back in time + if (isAbilityCanLookBackInTime(this) && isEventCanLookBackInTime(event)) { + // 603.10a leaves-the-battlefield + if (game.checkShortLivingLKI(affectedSourceId, Zone.BATTLEFIELD)) { + affectedObjectZone = Zone.BATTLEFIELD; + } + // 603.10a leaves a graveyard + // TODO: need tests + if (game.checkShortLivingLKI(affectedSourceId, Zone.GRAVEYARD)) { + affectedObjectZone = Zone.GRAVEYARD; + } + // 603.10a put into a hand or library + // TODO: need tests and implementation? } } - // TODO: research use cases and implement shared logic with "looking zone" instead LKI only - // 603.10b Abilities that trigger when a permanent phases out look back in time. - // 603.10c Abilities that trigger specifically when an object becomes unattached look back in time. - // 603.10d Abilities that trigger when a player loses control of an object look back in time. - // 603.10e Abilities that trigger when a spell is countered look back in time. - // 603.10f Abilities that trigger when a player loses the game look back in time. - // 603.10g Abilities that trigger when a player planeswalks away from a plane look back in time. - - return zone.match(sourceObjectZone); + return zone.match(affectedObjectZone); } - public static boolean isTriggerCanFireAfterLeaveBattlefield(GameEvent event) { + public static boolean isAbilityCanLookBackInTime(Ability ability) { + if (ability instanceof StaticAbility) { + return true; + } + if (ability instanceof TriggeredAbility) { + return ((TriggeredAbility) ability).isLeavesTheBattlefieldTrigger(); + } + return false; + } + + public static boolean isEventCanLookBackInTime(GameEvent event) { if (event == null) { return false; } @@ -1300,7 +1317,7 @@ public abstract class AbilityImpl implements Ability { } if (object == null) { - // replacement and other continues effects can be without source, but active (must return true all time) + // global replacement and other continues effects can be without source, but active (must return true all time) return true; } -- 2.47.2 From 8f3cd5a1064dc095f65c8fe4e26d2c8c3e51028f Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:02:59 -0500 Subject: [PATCH 40/40] [DSC] Implement Fear of Sleep Paralysis Also fix UntapSourceCost to allow 'consuming a stun counter' to count as paying untap cost, per rule 118.11, but only if Fear of Sleep Paralysis isn't making stun permanent. --- .../mage/cards/f/FearOfSleepParalysis.java | 92 +++++++++++++++++++ .../sets/DuskmournHouseOfHorrorCommander.java | 1 + .../costs/common/UntapSourceCost.java | 7 ++ 3 files changed, 100 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FearOfSleepParalysis.java diff --git a/Mage.Sets/src/mage/cards/f/FearOfSleepParalysis.java b/Mage.Sets/src/mage/cards/f/FearOfSleepParalysis.java new file mode 100644 index 00000000000..db977eb225d --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FearOfSleepParalysis.java @@ -0,0 +1,92 @@ +package mage.cards.f; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.abilityword.EerieAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.TapTargetEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Grath + */ +public final class FearOfSleepParalysis extends CardImpl { + + public FearOfSleepParalysis(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{5}{U}"); + + this.subtype.add(SubType.NIGHTMARE); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Eerie -- Whenever Fear of Sleep Paralysis or another enchantment you control enters and whenever you fully unlock a Room, tap up to one target creature and put a stun counter on it. + Ability ability = new EerieAbility(new TapTargetEffect()); + ability.addEffect(new AddCountersTargetEffect(CounterType.STUN.createInstance()).setText("and put a stun counter on it")); + ability.addTarget(new TargetCreaturePermanent(0, 1)); + this.addAbility(ability); + + // Stun counters can't be removed from permanents your opponents control. + this.addAbility(new SimpleStaticAbility(new FearOfSleepParalysisEffect())); + } + + private FearOfSleepParalysis(final FearOfSleepParalysis card) { + super(card); + } + + @Override + public FearOfSleepParalysis copy() { + return new FearOfSleepParalysis(this); + } +} + +class FearOfSleepParalysisEffect extends ReplacementEffectImpl { + + FearOfSleepParalysisEffect() { + super(Duration.WhileOnBattlefield, Outcome.PreventDamage); + staticText = "Stun counters can't be removed from permanents your opponents control"; + } + + private FearOfSleepParalysisEffect(final FearOfSleepParalysisEffect effect) { + super(effect); + } + + @Override + public FearOfSleepParalysisEffect copy() { + return new FearOfSleepParalysisEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + return true; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.REMOVE_COUNTERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Permanent target = game.getPermanent(event.getTargetId()); + return target != null && event.getData().equals(CounterType.STUN.getName()) && !target.getControllerId().equals(source.getControllerId()); + } + +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index 7353d9329a1..b8d5ef137f1 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -107,6 +107,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Ezuri's Predation", 178, Rarity.RARE, mage.cards.e.EzurisPredation.class)); cards.add(new SetCardInfo("Falkenrath Noble", 140, Rarity.UNCOMMON, mage.cards.f.FalkenrathNoble.class)); cards.add(new SetCardInfo("Fate Unraveler", 141, Rarity.RARE, mage.cards.f.FateUnraveler.class)); + cards.add(new SetCardInfo("Fear of Sleep Paralysis", 12, Rarity.RARE, mage.cards.f.FearOfSleepParalysis.class)); cards.add(new SetCardInfo("Feed the Swarm", 78, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class)); cards.add(new SetCardInfo("Fellwar Stone", 245, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class)); cards.add(new SetCardInfo("Flooded Grove", 276, Rarity.RARE, mage.cards.f.FloodedGrove.class)); diff --git a/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java b/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java index 09ec8fe5b03..2e379faac2a 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/UntapSourceCost.java @@ -6,6 +6,7 @@ import mage.abilities.Ability; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; import mage.constants.AsThoughEffectType; +import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; @@ -27,7 +28,13 @@ public class UntapSourceCost extends CostImpl { public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { Permanent permanent = game.getPermanent(source.getSourceId()); if (permanent != null) { + int stunCount = permanent.getCounters(game).getCount(CounterType.STUN); paid = permanent.untap(game); + // 118.11 - if a stun counter replaces the untap, the cost has still been paid. + // Fear of Sleep Paralysis ruling - if the stun counter can't be removed, the untap cost hasn't been paid. + if (stunCount > 0) { + paid = permanent.getCounters(game).getCount(CounterType.STUN) < stunCount; + } } return paid; } -- 2.47.2