diff --git a/Mage.Sets/src/mage/cards/a/AwakenTheSleeper.java b/Mage.Sets/src/mage/cards/a/AwakenTheSleeper.java index b3d25c5dbbf..be17db82987 100644 --- a/Mage.Sets/src/mage/cards/a/AwakenTheSleeper.java +++ b/Mage.Sets/src/mage/cards/a/AwakenTheSleeper.java @@ -18,6 +18,8 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; +import java.util.LinkedList; +import java.util.List; import java.util.UUID; /** @@ -73,12 +75,16 @@ class AwakenTheSleeperEffect extends OneShotEffect { || !player.chooseUse(outcome, "Destroy all equipment attached to " + permanent.getName() + '?', source, game)) { return false; } + List toDestroy = new LinkedList<>(); for (UUID attachmentId : permanent.getAttachments()) { Permanent attachment = game.getPermanent(attachmentId); if (attachment != null && attachment.hasSubtype(SubType.EQUIPMENT, game)) { - attachment.destroy(source, game); + toDestroy.add(attachment); } } + for (Permanent equipment : toDestroy) { + equipment.destroy(source, game); + } return true; } } diff --git a/Mage.Sets/src/mage/cards/w/WhirlwindDenial.java b/Mage.Sets/src/mage/cards/w/WhirlwindDenial.java index 1df06fd5811..e5657544fd4 100644 --- a/Mage.Sets/src/mage/cards/w/WhirlwindDenial.java +++ b/Mage.Sets/src/mage/cards/w/WhirlwindDenial.java @@ -8,14 +8,16 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.game.Game; +import mage.game.stack.StackObject; import mage.players.Player; import mage.util.ManaUtil; -import java.util.Objects; +import java.util.LinkedList; +import java.util.List; import java.util.UUID; /** - * @author TheElk801 + * @author xenohedron */ public final class WhirlwindDenial extends CardImpl { @@ -55,36 +57,46 @@ class WhirlwindDenialEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - game.getStack() - .stream() - .filter(Objects::nonNull) - .forEachOrdered(stackObject -> { - if (!game.getOpponents(source.getControllerId()).contains(stackObject.getControllerId())) { - return; - } - Player player = game.getPlayer(stackObject.getControllerId()); - if (player == null) { - return; - } - Cost cost = ManaUtil.createManaCost(4, false); - if (cost.canPay(source, source, stackObject.getControllerId(), game) - && player.chooseUse(outcome, "Pay {4} to prevent " - + stackObject.getIdName() + " from being countered?", source, game) - && cost.pay(source, game, source, stackObject.getControllerId(), false)) { - game.informPlayers("The cost was paid by " - + player.getLogName() - + " to prevent " - + stackObject.getIdName() - + " from being countered."); - return; - } - game.informPlayers("The cost was not paid by " - + player.getLogName() - + " to prevent " + List stackObjectsToCounter = new LinkedList<>(); + Cost cost = ManaUtil.createManaCost(4, false); + // As Whirlwind Denial resolves, first the opponent whose turn it is + // (or, if it's your turn, the next opponent in turn order) chooses which spells and/or abilities to pay for, + // then pays that amount. Then each other opponent in turn order does the same. + // Then all spells and abilities that weren't paid for are countered at the same time. + // (2020-01-24) + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + if (playerId.equals(source.getControllerId())) { + continue; // only opponents have to pay + } + Player player = game.getPlayer(playerId); + for (StackObject stackObject : game.getStack()) { + if (!playerId.equals(stackObject.getControllerId())) { + continue; // opponents only choose for their own spells/abilities + } + if (player == null) { // shouldn't be null, but if somehow so, they can't pay, so counter it + stackObjectsToCounter.add(stackObject); + continue; + } + if (cost.canPay(source, source, playerId, game) + && player.chooseUse(outcome, "Pay {4} to prevent " + + stackObject.getIdName() + " from being countered?", source, game) + && cost.pay(source, game, source, stackObject.getControllerId(), false)) { + game.informPlayers(player.getLogName() + + " pays the cost to prevent " + stackObject.getIdName() + " from being countered."); - game.getStack().counter(stackObject.getId(), source, game); - }); + } else { + game.informPlayers(stackObject.getIdName() + + " will be countered as " + + player.getLogName() + + " does not pay the cost."); + stackObjectsToCounter.add(stackObject); // will be countered all at the end + } + } + } + for (StackObject toCounter : stackObjectsToCounter) { + game.getStack().counter(toCounter.getId(), source, game); + } return true; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/thb/WhirlwindDenialTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/thb/WhirlwindDenialTest.java new file mode 100644 index 00000000000..382f78f7984 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/thb/WhirlwindDenialTest.java @@ -0,0 +1,58 @@ +package org.mage.test.cards.single.thb; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author xenohedron + */ + +public class WhirlwindDenialTest extends CardTestPlayerBase { + + private static final String denial = "Whirlwind Denial"; + private static final String guttersnipe = "Guttersnipe"; // trigger deals 2 damage on cast + private static final String tithe = "Blood Tithe"; // deals 3 damage, gain 3 life + + private void baseWhirlwindTest(boolean payTrigger, boolean paySpell) { + setStrictChooseMode(true); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Wastes", 3+4+4); + addCard(Zone.BATTLEFIELD, playerA, guttersnipe); + addCard(Zone.HAND, playerA, tithe); // Player A tries to cast Blood Tithe for 3 damage + 2 damage + addCard(Zone.BATTLEFIELD, playerB, "Island", 3); + addCard(Zone.HAND, playerB, denial); // Player B denies it + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, tithe); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, denial); + setChoice(playerA, payTrigger); + setChoice(playerA, paySpell); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerB, 20 - (payTrigger ? 2 : 0) - (paySpell ? 3 : 0)); + } + + @Test + public void testWhirlwindPayAllCosts() { + baseWhirlwindTest(true, true); + } + + @Test + public void testWhirlwindPayTrigger() { + baseWhirlwindTest(true, false); + } + + @Test + public void testWhirlwindPaySpell() { + baseWhirlwindTest(false, true); + } + + @Test + public void testWhirlwindPayNone() { + baseWhirlwindTest(false, false); + } + +}