diff --git a/Mage.Sets/src/mage/sets/futuresight/VenserShaperSavant.java b/Mage.Sets/src/mage/sets/futuresight/VenserShaperSavant.java index 1045ba09c7..ff25793eca 100644 --- a/Mage.Sets/src/mage/sets/futuresight/VenserShaperSavant.java +++ b/Mage.Sets/src/mage/sets/futuresight/VenserShaperSavant.java @@ -31,18 +31,11 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.keyword.FlashAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.Rarity; -import mage.constants.Zone; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.game.stack.Spell; -import mage.players.Player; import mage.target.Target; import mage.target.common.TargetSpellOrPermanent; @@ -65,7 +58,7 @@ public class VenserShaperSavant extends CardImpl { // Flash this.addAbility(FlashAbility.getInstance()); // When Venser, Shaper Savant enters the battlefield, return target spell or permanent to its owner's hand. - Ability ability = new EntersBattlefieldTriggeredAbility(new VenserShaperSavantEffect(), false); + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect(true), false); Target target = new TargetSpellOrPermanent(); ability.addTarget(target); this.addAbility(ability); @@ -80,56 +73,3 @@ public class VenserShaperSavant extends CardImpl { return new VenserShaperSavant(this); } } - -class VenserShaperSavantEffect extends OneShotEffect { - - public VenserShaperSavantEffect() { - super(Outcome.ReturnToHand); - this.staticText = "return target spell or permanent to its owner's hand"; - } - - public VenserShaperSavantEffect(final VenserShaperSavantEffect effect) { - super(effect); - } - - @Override - public VenserShaperSavantEffect copy() { - return new VenserShaperSavantEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Permanent permanent = game.getPermanent(this.getTargetPointer().getFirst(game, source)); - if (permanent != null) { - return controller.moveCards(permanent, null, Zone.HAND, source, game); - } - - /** - * 01.05.2007 If a spell is returned to its owner's hand, it's - * removed from the stack and thus will not resolve. The spell isn't - * countered; it just no longer exists. 01.05.2007 If a copy of a - * spell is returned to its owner's hand, it's moved there, then it - * will cease to exist as a state-based action. 01.05.2007 If - * Venser's enters-the-battlefield ability targets a spell cast with - * flashback, that spell will be exiled instead of returning to its - * owner's hand. - */ - Spell spell = game.getStack().getSpell(this.getTargetPointer().getFirst(game, source)); - if (spell != null) { - Card card = null; - if (!spell.isCopy()) { - card = spell.getCard(); - } - game.getStack().remove(spell); - if (card != null) { - controller.moveCards(card, null, Zone.HAND, source, game); - } - return true; - } - - } - return false; - } -} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/ReturnToHandTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/ReturnToHandTest.java index 9864c33cfd..5e55cb4d37 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/ReturnToHandTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/ReturnToHandTest.java @@ -36,7 +36,6 @@ import org.mage.test.serverside.base.CardTestPlayerBase; * * @author LevelX2 */ - public class ReturnToHandTest extends CardTestPlayerBase { /** @@ -47,23 +46,23 @@ public class ReturnToHandTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Pillarfield Ox"); // Bloodthirst 3 // Flying - // {R}{R}{R}: Return Skarrgan Firebird from your graveyard to your hand. Activate this ability only if an opponent was dealt damage this turn. + // {R}{R}{R}: Return Skarrgan Firebird from your graveyard to your hand. Activate this ability only if an opponent was dealt damage this turn. addCard(Zone.BATTLEFIELD, playerB, "Skarrgan Firebird"); addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); addCard(Zone.HAND, playerB, "Bone Splinters"); - + // As an additional cost to cast Bone Splinters, sacrifice a creature. // Destroy target creature. castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Bone Splinters", "Pillarfield Ox"); setChoice(playerB, "Skarrgan Firebird"); - + attack(2, playerB, "Silvercoat Lion"); - + activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{R}{R}{R}: Return"); setStopAt(2, PhaseStep.END_TURN); - + execute(); assertPermanentCount(playerA, "Skarrgan Firebird", 0); @@ -79,40 +78,71 @@ public class ReturnToHandTest extends CardTestPlayerBase { @Test public void VeilbornGhoulTest1() { // Veilborn Ghoul can't block. - // Whenever a Swamp enters the battlefield under your control, you may return Veilborn Ghoul from your graveyard to your hand. + // Whenever a Swamp enters the battlefield under your control, you may return Veilborn Ghoul from your graveyard to your hand. addCard(Zone.GRAVEYARD, playerA, "Veilborn Ghoul"); addCard(Zone.HAND, playerA, "Swamp"); - + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp"); setStopAt(1, PhaseStep.BEGIN_COMBAT); - + execute(); - assertPermanentCount(playerA, "Swamp", 1); + assertPermanentCount(playerA, "Swamp", 1); assertHandCount(playerA, "Veilborn Ghoul", 1); } + /** - * Return from graveyard to hand if you play a non swamp land but Urborg, Tomb of Yawgmoth is in play + * Return from graveyard to hand if you play a non swamp land but Urborg, + * Tomb of Yawgmoth is in play */ @Test public void VeilbornGhoulTest2() { // Veilborn Ghoul can't block. - // Whenever a Swamp enters the battlefield under your control, you may return Veilborn Ghoul from your graveyard to your hand. + // Whenever a Swamp enters the battlefield under your control, you may return Veilborn Ghoul from your graveyard to your hand. addCard(Zone.GRAVEYARD, playerA, "Veilborn Ghoul"); addCard(Zone.HAND, playerA, "Flood Plain"); - + // Each land is a Swamp in addition to its other land types. addCard(Zone.BATTLEFIELD, playerA, "Urborg, Tomb of Yawgmoth", 1); - + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flood Plain"); setStopAt(1, PhaseStep.BEGIN_COMBAT); - + execute(); - assertPermanentCount(playerA, "Flood Plain", 1); + assertPermanentCount(playerA, "Flood Plain", 1); assertHandCount(playerA, "Veilborn Ghoul", 1); - } - + } + + /** + * Return a spell from stack to Hand + */ + @Test + public void BrutalExpulsionTest() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // Devoid + // Choose one or both + // - Return target spell or creature to its owner's hand; + // or Brutal Expulsion deals 2 damage to target creature or planeswalker. If that permanent would be put into a graveyard this turn, exile it instead. + addCard(Zone.HAND, playerA, "Brutal Expulsion"); // {2}{U}{R} + + addCard(Zone.BATTLEFIELD, playerB, "Plains", 4); + addCard(Zone.HAND, playerB, "Pillarfield Ox", 1); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Pillarfield Ox"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Brutal Expulsion", "mode=1Pillarfield Ox^mode=2Silvercoat Lion", "Pillarfield Ox"); + setStopAt(2, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertGraveyardCount(playerA, "Brutal Expulsion", 1); + assertExileCount("Silvercoat Lion", 1); + assertPermanentCount(playerB, "Pillarfield Ox", 0); + assertHandCount(playerB, "Pillarfield Ox", 1); + + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index f438ff6ed5..26e68f0944 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -935,7 +935,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement * @param step * @param player * @param cardName - * @param targetName + * @param targetName for modal spells add the mode to the name e.g. + * "mode=2SilvercoatLion^mode3=PillarfieldOx" * @param spellOnStack */ public void castSpell(int turnNum, PhaseStep step, TestPlayer player, String cardName, String targetName, String spellOnStack) { diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 6a6f5efd5e..0c0d8ebd7d 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -2934,6 +2934,18 @@ public abstract class PlayerImpl implements Player, Serializable { } } else { Card card = game.getCard(cardId); + if (card == null) { + Spell spell = game.getState().getStack().getSpell(cardId); + if (spell != null) { + if (!spell.isCopy()) { + card = spell.getCard(); + } else { + // If a spell is returned to its owner's hand, it's removed from the stack and thus will not resolve + game.getStack().remove(spell); + game.informPlayers(spell.getLogName() + " was removed from the stack"); + } + } + } if (card != null) { cardList.add(card); } @@ -2973,6 +2985,13 @@ public abstract class PlayerImpl implements Player, Serializable { case HAND: for (Card card : cards) { fromZone = game.getState().getZone(card.getId()); + if (fromZone == Zone.STACK) { + // If a spell is returned to its owner's hand, it's removed from the stack and thus will not resolve + Spell spell = game.getStack().getSpell(card.getId()); + if (spell != null) { + game.getStack().remove(spell); + } + } boolean hideCard = fromZone.equals(Zone.LIBRARY) || (card.isFaceDown(game) && !fromZone.equals(Zone.STACK) && !fromZone.equals(Zone.BATTLEFIELD)); if (moveCardToHandWithInfo(card, source == null ? null : source.getSourceId(), game, !hideCard)) {