From a22e89d26a2f57fab98663be09c39efc18edb715 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 11 Jun 2017 10:30:56 +0200 Subject: [PATCH] * Added test for spells targeting players continue resolving even when those players leave the game (closes #1600). Problem no longer reproducable. --- .../src/mage/cards/t/TendrilsOfAgony.java | 7 +- .../PlayerDiedStackTargetHandlingTest.java | 101 ++++++++++++++++++ 2 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerDiedStackTargetHandlingTest.java diff --git a/Mage.Sets/src/mage/cards/t/TendrilsOfAgony.java b/Mage.Sets/src/mage/cards/t/TendrilsOfAgony.java index ad70967486e..9b4fbfc959d 100644 --- a/Mage.Sets/src/mage/cards/t/TendrilsOfAgony.java +++ b/Mage.Sets/src/mage/cards/t/TendrilsOfAgony.java @@ -28,12 +28,12 @@ package mage.cards.t; import java.util.UUID; -import mage.constants.CardType; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.abilities.keyword.StormAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.CardType; import mage.target.TargetPlayer; /** @@ -43,14 +43,13 @@ import mage.target.TargetPlayer; public class TendrilsOfAgony extends CardImpl { public TendrilsOfAgony(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{B}{B}"); - + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}{B}"); // Target player loses 2 life and you gain 2 life. this.getSpellAbility().addTarget(new TargetPlayer()); this.getSpellAbility().addEffect(new LoseLifeTargetEffect(2)); this.getSpellAbility().addEffect(new GainLifeEffect(2)); - // Storm + // Storm (When you cast this spell, copy it for each spell cast before it this turn. You may choose new targets for the copies.) this.addAbility(new StormAbility()); } diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerDiedStackTargetHandlingTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerDiedStackTargetHandlingTest.java new file mode 100644 index 00000000000..8f240a2378e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PlayerDiedStackTargetHandlingTest.java @@ -0,0 +1,101 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.multiplayer; + +import java.io.FileNotFoundException; +import mage.constants.MultiplayerAttackOption; +import mage.constants.PhaseStep; +import mage.constants.RangeOfInfluence; +import mage.constants.Zone; +import mage.game.FreeForAll; +import mage.game.Game; +import mage.game.GameException; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestMultiPlayerBase; + +/** + * + * @author LevelX2 + */ +public class PlayerDiedStackTargetHandlingTest extends CardTestMultiPlayerBase { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + // Start Life = 2 + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 3); + // Player order: A -> D -> C -> B + playerA = createPlayer(game, playerA, "PlayerA"); + playerB = createPlayer(game, playerB, "PlayerB"); + playerC = createPlayer(game, playerC, "PlayerC"); + playerD = createPlayer(game, playerD, "PlayerD"); + return game; + } + + /** + * If a spell or ability on the stack is targeting a player (or an object + * controlled by that player), and that player leaves the game, then the + * target becomes illegal and the spell or ability should be countered upon + * resolution (assuming that player or object is the only target of the + * spell). However, Xmage regularly continues resolving spells and abilities + * targeting players after that player concedes, which is a problem in + * multiplayer. + */ + @Test + public void TestDeadPlayerIsNoLongerValidTarget() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // Lightning Helix deals 3 damage to target creature or player and you gain 3 life. + addCard(Zone.HAND, playerA, "Lightning Helix", 2); // Instant {R}{W} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Helix", playerD); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Helix", playerD); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, "Lightning Helix", 2); + Assert.assertTrue("Active player has to be player C", currentGame.getActivePlayerId() == playerC.getId()); + + assertLife(playerA, 6); + + } + + /** + * Player A casts Tendrils of Agony with a storm count of 10 targeting + * Player B, who is at 10 life. After the first five copies of Tendrils + * resolve, player A will have gained 10 life and Player B will have lost 10 + * life, so player B should die as a state-based action before the remaining + * six copies resolve. In similar circumstances, Xmage has continued + * resolving the additional six copies, putting player B at -12 life and + * letting player A gain an additional 12 life inappropriately + */ + @Test + public void TestDeadPlayerIsNoLongerValidTarget2() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.HAND, playerA, "Silvercoat Lion", 2); + // Target player loses 2 life and you gain 2 life. + // Storm (When you cast this spell, copy it for each spell cast before it this turn. You may choose new targets for the copies.) + addCard(Zone.HAND, playerA, "Tendrils of Agony", 1); // Sorcery {2}{B}{B} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tendrils of Agony", playerD); + addTarget(playerA, playerD); + addTarget(playerA, playerD); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Silvercoat Lion", 2); + assertGraveyardCount(playerA, "Tendrils of Agony", 1); + Assert.assertTrue("Active player has to be player C", currentGame.getActivePlayerId() == playerC.getId()); + + assertLife(playerA, 7); + + } +}