diff --git a/Mage.Sets/src/mage/cards/t/TeferisImp.java b/Mage.Sets/src/mage/cards/t/TeferisImp.java new file mode 100644 index 00000000000..d0da80cc8e2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TeferisImp.java @@ -0,0 +1,54 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.common.SourcePhaseInTriggeredAbility; +import mage.abilities.common.SourcePhaseOutTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.PhasingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class TeferisImp extends CardImpl { + + public TeferisImp(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + + this.subtype.add(SubType.IMP); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Phasing + this.addAbility(PhasingAbility.getInstance()); + + // Whenever Teferi's Imp phases out, discard a card. + this.addAbility(new SourcePhaseOutTriggeredAbility( + new DiscardControllerEffect(1), false) + ); + + // Whenever Teferi's Imp phases in, draw a card. + this.addAbility(new SourcePhaseInTriggeredAbility( + new DrawCardSourceControllerEffect(1), false) + ); + } + + private TeferisImp(final TeferisImp card) { + super(card); + } + + @Override + public TeferisImp copy() { + return new TeferisImp(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Mirage.java b/Mage.Sets/src/mage/sets/Mirage.java index 47d804ddfc6..c3838400e84 100644 --- a/Mage.Sets/src/mage/sets/Mirage.java +++ b/Mage.Sets/src/mage/sets/Mirage.java @@ -309,6 +309,7 @@ public final class Mirage extends ExpansionSet { cards.add(new SetCardInfo("Teeka's Dragon", 320, Rarity.RARE, mage.cards.t.TeekasDragon.class)); cards.add(new SetCardInfo("Teferi's Curse", 96, Rarity.COMMON, mage.cards.t.TeferisCurse.class)); cards.add(new SetCardInfo("Teferi's Drake", 97, Rarity.COMMON, mage.cards.t.TeferisDrake.class)); + cards.add(new SetCardInfo("Teferi's Imp", 98, Rarity.RARE, mage.cards.t.TeferisImp.class)); cards.add(new SetCardInfo("Teferi's Isle", 330, Rarity.RARE, mage.cards.t.TeferisIsle.class)); cards.add(new SetCardInfo("Telim'Tor", 197, Rarity.RARE, mage.cards.t.TelimTor.class)); cards.add(new SetCardInfo("Telim'Tor's Darts", 321, Rarity.UNCOMMON, mage.cards.t.TelimTorsDarts.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/TeferisImpTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/TeferisImpTest.java new file mode 100644 index 00000000000..c4fcd2e8a7d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mir/TeferisImpTest.java @@ -0,0 +1,51 @@ +package org.mage.test.cards.single.mir; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class TeferisImpTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.t.TeferisImp}
+ * Teferi's Imp {2}{U}
+ * Creature — Imp
+ * Flying
+ * Phasing
+ * Whenever Teferi’s Imp phases out, discard a card.
+ * Whenever Teferi’s Imp phases in, draw a card.
+ * 1/1 + */ + private static final String imp = "Teferi's Imp"; + + @Test + public void test_Phasing_triggers() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, imp); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.HAND, playerA, "Grizzly Bears", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, imp); + + checkHandCount("before discard", 2, PhaseStep.END_TURN, playerA, 2); + // Beginning of turn 3 -- imp phases out. trigger to discard. + setChoice(playerA, "Grizzly Bears"); // choose to discard one of the Bears + waitStackResolved(3, PhaseStep.UPKEEP); + checkHandCount("after discard", 3, PhaseStep.UPKEEP, playerA, 1); + + checkHandCount("before draw", 4, PhaseStep.END_TURN, playerA, 2); + // Beginning of turn 5 -- imp phases in. trigger to draw. + waitStackResolved(5, PhaseStep.UPKEEP); + checkHandCount("after draw", 5, PhaseStep.UPKEEP, playerA, 3); + + setStopAt(5, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Grizzly Bears", 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java index 23a690ec944..b017418652c 100644 --- a/Mage/src/main/java/mage/abilities/Ability.java +++ b/Mage/src/main/java/mage/abilities/Ability.java @@ -419,6 +419,22 @@ public interface Ability extends Controllable, Serializable { */ void setWorksFaceDown(boolean worksFaceDown); + /** + * Returns true if this ability has to work also with phased out object. + * + * @return + */ + boolean getWorksPhasedOut(); + + /** + * Sets the value for the worksPhasedOut flag + *

+ * true = the ability works also if the object is phased out + * + * @param worksPhasedOut + */ + void setWorksPhasedOut(boolean worksPhasedOut); + /** * Returns true if this ability's rule is visible on the card tooltip * diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 59b6575e368..9cdf2ed6802 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -70,6 +70,7 @@ public abstract class AbilityImpl implements Ability { protected boolean ruleAdditionalCostsVisible = true; protected boolean activated = false; protected boolean worksFaceDown = false; + protected boolean worksPhasedOut = false; protected int sourceObjectZoneChangeCounter; protected List watchers = new ArrayList<>(); // access to it by GetWatchers only (it can be overridden by some abilities) protected List subAbilities = null; @@ -121,6 +122,7 @@ public abstract class AbilityImpl implements Ability { this.ruleVisible = ability.ruleVisible; this.ruleAdditionalCostsVisible = ability.ruleAdditionalCostsVisible; this.worksFaceDown = ability.worksFaceDown; + this.worksPhasedOut = ability.worksPhasedOut; this.abilityWord = ability.abilityWord; this.flavorWord = ability.flavorWord; this.sourceObjectZoneChangeCounter = ability.sourceObjectZoneChangeCounter; @@ -1050,7 +1052,9 @@ public abstract class AbilityImpl implements Ability { } if (object != null) { if (object instanceof Permanent) { - return object.hasAbility(this, game) && ((Permanent) object).isPhasedIn(); + return object.hasAbility(this, game) && ( + ((Permanent) object).isPhasedIn() || this.getWorksPhasedOut() + ); } else { // cards and other objects return object.hasAbility(this, game); @@ -1270,6 +1274,16 @@ public abstract class AbilityImpl implements Ability { this.worksFaceDown = worksFaceDown; } + @Override + public boolean getWorksPhasedOut() { + return worksPhasedOut; + } + + @Override + public void setWorksPhasedOut(boolean worksPhasedOut) { + this.worksPhasedOut = worksPhasedOut; + } + @Override public MageObject getSourceObject(Game game) { return game.getObject(getSourceId()); @@ -1481,7 +1495,7 @@ public abstract class AbilityImpl implements Ability { // to change the zone of any existing singleton abilities return this; } - AbilityImpl copy = ((AbilityImpl)this.copy()); + AbilityImpl copy = ((AbilityImpl) this.copy()); copy.zone = zone; copy.newId(); return copy; diff --git a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java index 4dd7210e07d..ab265842d65 100644 --- a/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java @@ -315,7 +315,6 @@ public abstract class TriggeredAbilityImpl extends AbilityImpl implements Trigge source = game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD); } break; - case PHASED_OUT: case PHASED_IN: if (isLeavesTheBattlefieldTrigger()) { source = game.getLastKnownInformation(getSourceId(), event.getZone()); diff --git a/Mage/src/main/java/mage/abilities/common/SourcePhaseOutTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/SourcePhaseOutTriggeredAbility.java new file mode 100644 index 00000000000..93b3ce44662 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/SourcePhaseOutTriggeredAbility.java @@ -0,0 +1,38 @@ +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * @author Susucr + */ +public class SourcePhaseOutTriggeredAbility extends TriggeredAbilityImpl { + + public SourcePhaseOutTriggeredAbility(Effect effect, boolean optional) { + super(Zone.BATTLEFIELD, effect, optional); + setTriggerPhrase("Whenever {this} phases out, "); + setWorksPhasedOut(true); + } + + protected SourcePhaseOutTriggeredAbility(final SourcePhaseOutTriggeredAbility ability) { + super(ability); + } + + @Override + public SourcePhaseOutTriggeredAbility copy() { + return new SourcePhaseOutTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.PHASED_OUT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return getSourceId().equals(event.getTargetId()); + } +} diff --git a/Mage/src/main/java/mage/constants/Zone.java b/Mage/src/main/java/mage/constants/Zone.java index 90162f51daf..adb91001e6b 100644 --- a/Mage/src/main/java/mage/constants/Zone.java +++ b/Mage/src/main/java/mage/constants/Zone.java @@ -2,7 +2,6 @@ package mage.constants; /** - * * @author North */ public enum Zone { diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index 880c952fd67..0e95af451a0 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -540,6 +540,16 @@ public class StackAbility extends StackObjectImpl implements Ability { this.ability.setWorksFaceDown(worksFaceDown); } + @Override + public boolean getWorksPhasedOut() { + return this.ability.getWorksPhasedOut(); + } + + @Override + public void setWorksPhasedOut(boolean worksPhasedOut) { + this.ability.setWorksPhasedOut(worksPhasedOut); + } + @Override public List getWatchers() { return this.ability.getWatchers();