diff --git a/Mage.Sets/src/mage/cards/g/GraspOfFate.java b/Mage.Sets/src/mage/cards/g/GraspOfFate.java index ef573d7f546..b2bf8c94ba9 100644 --- a/Mage.Sets/src/mage/cards/g/GraspOfFate.java +++ b/Mage.Sets/src/mage/cards/g/GraspOfFate.java @@ -44,7 +44,6 @@ import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; -import static mage.filter.predicate.permanent.ControllerControlsIslandPredicate.filter; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -59,11 +58,11 @@ import mage.util.CardUtil; public class GraspOfFate extends CardImpl { public GraspOfFate(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}{W}"); // When Grasp of Fate enters the battlefield, for each opponent, exile up to one target nonland permanent that player controls until Grasp of Fate leaves the battlefield. Ability ability = new EntersBattlefieldTriggeredAbility(new GraspOfFateExileEffect()); - ability.addTarget(new TargetPermanent(filter)); + ability.addTarget(new TargetPermanent()); ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new OnLeaveReturnExiledToBattlefieldAbility())); this.addAbility(ability); } @@ -99,7 +98,7 @@ class GraspOfFateExileEffect extends OneShotEffect { public GraspOfFateExileEffect() { super(Outcome.Benefit); - this.staticText = "exile up to one target nonland permanent that player controls until {this} leaves the battlefield"; + this.staticText = "for each opponent, exile up to one target nonland permanent that player controls until {this} leaves the battlefield"; } public GraspOfFateExileEffect(final GraspOfFateExileEffect effect) { @@ -114,7 +113,7 @@ class GraspOfFateExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { + if (permanent != null) { // 11/4/2015: If Grasp of Fate leaves the battlefield before its triggered ability resolves, no nonland permanents will be exiled. return new ConditionalOneShotEffect(new ExileTargetEffect(CardUtil.getCardExileZoneId(game, source), permanent.getIdName(), Zone.BATTLEFIELD, true), SourceOnBattlefieldCondition.instance).apply(game, source); } return false; diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRangeAllTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRangeAllTest.java index 3954e058572..c017d3975da 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRangeAllTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerLeftGameRangeAllTest.java @@ -307,4 +307,47 @@ public class PlayerLeftGameRangeAllTest extends CardTestMultiPlayerBase { assertLife(playerA, 4); } + + /** + * * 11/4/2015: In a multiplayer game, if Grasp of Fate's owner leaves the + * game, the exiled cards will return to the battlefield. Because the + * one-shot effect that returns the cards isn't an ability that goes on the + * stack, it won't cease to exist along with the leaving player's spells and + * abilities on the stack. + */ + @Test + public void TestGraspOfFateReturn() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 3); + // When Grasp of Fate enters the battlefield, for each opponent, exile up to one target nonland permanent that player + // controls until Grasp of Fate leaves the battlefield. + addCard(Zone.HAND, playerA, "Grasp of Fate"); // Enchantment {1}{W}{W} + + addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1); + + addCard(Zone.HAND, playerC, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerC, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerC, "Juggernaut", 1); + + addCard(Zone.BATTLEFIELD, playerD, "Silvercoat Lion", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grasp of Fate"); + addTarget(playerA, "Pillarfield Ox"); + addTarget(playerA, "Juggernaut"); + addTarget(playerA, "Silvercoat Lion"); + + castSpell(2, PhaseStep.BEGIN_COMBAT, playerC, "Lightning Bolt", playerA); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerC, "Lightning Bolt", 1); + + assertLife(playerA, -1); + Assert.assertFalse("Player D is no longer in the game", playerA.isInGame()); + + assertPermanentCount(playerB, "Pillarfield Ox", 1); + assertPermanentCount(playerC, "Juggernaut", 1); + assertPermanentCount(playerD, "Silvercoat Lion", 1); + + } } diff --git a/Mage/src/main/java/mage/abilities/common/delayed/OnLeaveReturnExiledToBattlefieldAbility.java b/Mage/src/main/java/mage/abilities/common/delayed/OnLeaveReturnExiledToBattlefieldAbility.java index af95c8eaeed..8efd754402e 100644 --- a/Mage/src/main/java/mage/abilities/common/delayed/OnLeaveReturnExiledToBattlefieldAbility.java +++ b/Mage/src/main/java/mage/abilities/common/delayed/OnLeaveReturnExiledToBattlefieldAbility.java @@ -49,6 +49,12 @@ import mage.util.CardUtil; * * Uses no stack * + * 11/4/2015: In a multiplayer game, if Grasp of Fate's owner leaves the game, + * the exiled cards will return to the battlefield. Because the one-shot effect + * that returns the cards isn't an ability that goes on the stack, it won't + * cease to exist along with the leaving player's spells and abilities on the + * stack. + * * @author LevelX2 */ public class OnLeaveReturnExiledToBattlefieldAbility extends DelayedTriggeredAbility { diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 2514565d979..9a3375fd2b9 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -2493,6 +2493,7 @@ public abstract class GameImpl implements Game, Serializable { return; } //20100423 - 800.4a + Set toOutside = new HashSet<>(); for (Iterator it = getBattlefield().getAllPermanents().iterator(); it.hasNext();) { Permanent perm = it.next(); if (perm.getOwnerId().equals(playerId)) { @@ -2511,7 +2512,8 @@ public abstract class GameImpl implements Game, Serializable { if (perm.isCreature() && this.getCombat() != null) { perm.removeFromCombat(this, true); } - it.remove(); + toOutside.add(perm); +// it.remove(); } else if (perm.getControllerId().equals(player.getId())) { // and any effects which give that player control of any objects or players end Effects: @@ -2531,6 +2533,18 @@ public abstract class GameImpl implements Game, Serializable { } } } + // needed to send event that permanent leaves the battlefield to allow non stack effects to execute + player.moveCards(toOutside, Zone.OUTSIDE, null, this); + // triggered abilities that don't use the stack have to be executed + List abilities = state.getTriggered(player.getId()); + for (Iterator it = abilities.iterator(); it.hasNext();) { + TriggeredAbility triggeredAbility = it.next(); + if (!triggeredAbility.isUsesStack()) { + state.removeTriggeredAbility(triggeredAbility); + player.triggerAbility(triggeredAbility, this); + it.remove(); + } + } // Then, if that player controlled any objects on the stack not represented by cards, those objects cease to exist. this.getState().getContinuousEffects().removeInactiveEffects(this); getStack().removeIf(object -> object.getControllerId().equals(playerId)); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index e3c2e66df17..c850a67fcba 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -3461,6 +3461,16 @@ public abstract class PlayerImpl implements Player, Serializable { } } break; + case OUTSIDE: + for (Card card : cards) { + if (card instanceof Permanent) { + game.getBattlefield().removePermanent(((Permanent) card).getId()); + ZoneChangeEvent event = new ZoneChangeEvent(card.getId(), (source == null ? null : source.getSourceId()), + byOwner ? card.getOwnerId() : getId(), Zone.BATTLEFIELD, Zone.OUTSIDE, appliedEffects); + game.fireEvent(event); + } + } + break; default: throw new UnsupportedOperationException("to Zone" + toZone.toString() + " not supported yet"); }