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<>();