diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ThragtuskTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ThragtuskTest.java index 29ffd2b587a..5d1c8362b3e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ThragtuskTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ThragtuskTest.java @@ -83,7 +83,6 @@ public class ThragtuskTest extends CardTestPlayerBase { */ @Test - @Ignore // test fails because of bug public void testPhyrexianMetamorphTurnToFrog() { addCard(Zone.BATTLEFIELD, playerA, "Island", 4); // You may have Phyrexian Metamorph enter the battlefield as a copy of any artifact or creature on the battlefield, except it's an artifact in addition to its other types @@ -91,6 +90,7 @@ public class ThragtuskTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Island", 2); addCard(Zone.BATTLEFIELD, playerB, "Swamp", 6); + addCard(Zone.HAND, playerB, "Turn to Frog", 1); addCard(Zone.HAND, playerB, "Public Execution", 1); addCard(Zone.BATTLEFIELD, playerB, "Thragtusk", 1); @@ -104,7 +104,10 @@ public class ThragtuskTest extends CardTestPlayerBase { setStopAt(1, PhaseStep.END_TURN); execute(); + assertGraveyardCount(playerB,"Turn to Frog", 1); + assertPermanentCount(playerB, "Thragtusk", 1); + assertPermanentCount(playerA, "Thragtusk", 0); assertGraveyardCount(playerA,"Phyrexian Metamorph", 1); assertGraveyardCount(playerB,"Public Execution", 1); diff --git a/Mage/src/mage/abilities/Ability.java b/Mage/src/mage/abilities/Ability.java index 7d39b32193c..a4c67c4dc75 100644 --- a/Mage/src/mage/abilities/Ability.java +++ b/Mage/src/mage/abilities/Ability.java @@ -388,6 +388,17 @@ public interface Ability extends Controllable, Serializable { */ boolean isInUseableZone(Game game, MageObject source, 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) + * + * @param game + * @param source + * @param event + * @return + */ + boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event); + /** * Returns true if this ability has to be shown as topmost of all the rules of the object * diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index 3ad6958cd7b..deb14f6475c 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -873,6 +873,9 @@ public abstract class AbilityImpl implements Ability { */ @Override public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + if (!this.hasSourceObjectAbility(game, source, event)) { + return false; + } if (zone.equals(Zone.COMMAND)) { if (this.getSourceId() == null) { // commander effects return true; @@ -884,18 +887,31 @@ public abstract class AbilityImpl implements Ability { } } - MageObject object; 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) { - object = source; parameterSourceId = source.getId(); } else { - object = game.getObject(getSourceId()); parameterSourceId = getSourceId(); } - + // check agains shortLKI for effects that move multiple object at the same time (e.g. destroy all) + if (game.getShortLivingLKI(getSourceId(), getZone())) { + return true; + } + // check against current state + Zone test = game.getState().getZone(parameterSourceId); + return test != null && zone.match(test); + } + + @Override + public boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event) { + MageObject object = source; + // for singleton abilities like Flying we can't rely on abilities' source because it's only once in continuous effects + // so will use the sourceId of the object itself that came as a parameter if it is not null + if (object == null) { + object = game.getObject(getSourceId()); + } if (object != null && !object.getAbilities().contains(this)) { if (object instanceof Permanent) { return false; @@ -907,15 +923,9 @@ public abstract class AbilityImpl implements Ability { } } } - // check agains shortLKI for effects that move multiple object at the same time (e.g. destroy all) - if (game.getShortLivingLKI(getSourceId(), getZone())) { - return true; - } - // check against current state - Zone test = game.getState().getZone(parameterSourceId); - return test != null && zone.match(test); + return true; } - + @Override public String toString() { return getRule(); diff --git a/Mage/src/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/mage/abilities/TriggeredAbilityImpl.java index e6c95e0eca1..26806511a63 100644 --- a/Mage/src/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/mage/abilities/TriggeredAbilityImpl.java @@ -35,8 +35,10 @@ import mage.constants.AbilityType; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; import mage.game.events.ZoneChangeEvent; import mage.players.Player; +import org.apache.log4j.Logger; /** * @@ -44,6 +46,8 @@ import mage.players.Player; */ public abstract class TriggeredAbilityImpl extends AbilityImpl implements TriggeredAbility { + private static final transient Logger logger = Logger.getLogger(TriggeredAbilityImpl.class); + protected boolean optional; public TriggeredAbilityImpl(Zone zone, Effect effect) { @@ -161,6 +165,7 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge @Override public boolean isInUseableZone(Game game, MageObject source, 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 @@ -176,15 +181,24 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge if (event != null && event.getTargetId() != null && event.getTargetId().equals(getSourceId())) { switch (event.getType()) { case ZONE_CHANGE: - if (source == null && ((ZoneChangeEvent)event).getTarget() != null) { - source = ((ZoneChangeEvent)event).getTarget(); - } case DESTROYED_PERMANENT: - // case LOST_CONTROL: + if (event.getType().equals(EventType.DESTROYED_PERMANENT)) { + source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); + } else { + if (((ZoneChangeEvent)event).getTarget() != null) { + source = ((ZoneChangeEvent)event).getTarget(); + if (source.getName().equals("Thragtusk")) { + logger.info(source.getAbilities().toString()); + } + } else { + source = game.getLastKnownInformation(getSourceId(), ((ZoneChangeEvent)event).getZone()); + } + } + case PHASED_OUT: case PHASED_IN: if (this.zone == Zone.ALL || game.getLastKnownInformation(getSourceId(), zone) != null) { - return true; + return this.hasSourceObjectAbility(game, source, event); } } } diff --git a/Mage/src/mage/abilities/common/CycleTriggeredAbility.java b/Mage/src/mage/abilities/common/CycleTriggeredAbility.java index 835be92e466..ea8351356eb 100644 --- a/Mage/src/mage/abilities/common/CycleTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/CycleTriggeredAbility.java @@ -57,7 +57,7 @@ public class CycleTriggeredAbility extends ZoneChangeTriggeredAbility { @Override public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return true; + return game.getState().getZone(getSourceId()).equals(Zone.HAND) && hasSourceObjectAbility(game, source, event); } @Override diff --git a/Mage/src/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java b/Mage/src/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java index d4f3fa7ec29..2e8aefd917d 100644 --- a/Mage/src/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/DiesThisOrAnotherCreatureTriggeredAbility.java @@ -70,7 +70,16 @@ public class DiesThisOrAnotherCreatureTriggeredAbility extends TriggeredAbilityI @Override public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD || game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD) != null; + Permanent sourcePermanent; + if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) { + sourcePermanent = game.getPermanent(getSourceId()); + } else { + sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); + } + if (sourcePermanent == null) { + return false; + } + return hasSourceObjectAbility(game, sourcePermanent, event); } @Override diff --git a/Mage/src/mage/abilities/common/DiesTriggeredAbility.java b/Mage/src/mage/abilities/common/DiesTriggeredAbility.java index 7583252a37d..412d9518f09 100644 --- a/Mage/src/mage/abilities/common/DiesTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/DiesTriggeredAbility.java @@ -58,6 +58,9 @@ public class DiesTriggeredAbility extends ZoneChangeTriggeredAbility { public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { // check it was previously on battlefield Permanent before = ((ZoneChangeEvent) event).getTarget(); + if (!this.hasSourceObjectAbility(game, before, event)) { + return false; + } // check now it is in graveyard Zone after = game.getState().getZone(sourceId); return before != null && after != null && Zone.GRAVEYARD.match(after); diff --git a/Mage/src/mage/abilities/common/ExploitCreatureTriggeredAbility.java b/Mage/src/mage/abilities/common/ExploitCreatureTriggeredAbility.java index e0bd1c08c7b..f8573a1ae4a 100644 --- a/Mage/src/mage/abilities/common/ExploitCreatureTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/ExploitCreatureTriggeredAbility.java @@ -72,6 +72,9 @@ public class ExploitCreatureTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { if (event.getTargetId().equals(getSourceId()) && event.getSourceId().equals(getSourceId())) { + if (!this.hasSourceObjectAbility(game, source, event)) { + return false; + } return true; // if Exploits creature sacrifices itself, exploit triggers } return super.isInUseableZone(game, source, event); diff --git a/Mage/src/mage/abilities/keyword/HauntAbility.java b/Mage/src/mage/abilities/keyword/HauntAbility.java index 758b0686d21..76e3ebc6f09 100644 --- a/Mage/src/mage/abilities/keyword/HauntAbility.java +++ b/Mage/src/mage/abilities/keyword/HauntAbility.java @@ -143,7 +143,7 @@ class HauntExileAbility extends ZoneChangeTriggeredAbility { private boolean creatureHaunt; - // TODO: It's not checked yet, if the Haunt spell was resoved (and not countered or removed from stack). + // TODO: It's not checked yet, if the Haunt spell has resolved (and was not countered or removed from stack). public HauntExileAbility(boolean creatureHaunt) { super(creatureHaunt ? Zone.BATTLEFIELD: Zone.STACK, Zone.GRAVEYARD, new HauntEffect(), null, false); @@ -159,12 +159,16 @@ class HauntExileAbility extends ZoneChangeTriggeredAbility { } @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { + public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { boolean fromOK = true; - if (creatureHaunt) { + Permanent sourcePermanent = (Permanent) game.getLastKnownInformation(sourceId, Zone.BATTLEFIELD); + if (creatureHaunt && sourcePermanent == null) { // check it was previously on battlefield - fromOK = game.getLastKnownInformation(sourceId, Zone.BATTLEFIELD) != null; + fromOK = false; } + if (!this.hasSourceObjectAbility(game, sourcePermanent, event)) { + return false; + } // check now it is in graveyard Zone after = game.getState().getZone(sourceId); return fromOK && after != null && Zone.GRAVEYARD.match(after); diff --git a/Mage/src/mage/game/stack/StackAbility.java b/Mage/src/mage/game/stack/StackAbility.java index b9826bd2a33..d4ae3244443 100644 --- a/Mage/src/mage/game/stack/StackAbility.java +++ b/Mage/src/mage/game/stack/StackAbility.java @@ -408,7 +408,12 @@ public class StackAbility implements StackObject, Ability { @Override public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - throw new UnsupportedOperationException("Not supported yet."); + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean hasSourceObjectAbility(Game game, MageObject source, GameEvent event) { + throw new UnsupportedOperationException("Not supported."); } @Override