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