From 63851b73a1608b4410563e0970c530bcaaf7c975 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 12 Jun 2020 07:42:36 -0400 Subject: [PATCH] Updated commander zone change rules (ready for review) (#6620) * updated commander zone change rules * moved commander tracking into game state * fixed a zone change error * fixed some more tests for new commander rule * updated variable names * updated a test name --- .../commander/duel/CastBRGCommanderTest.java | 2 +- .../duel/CommanderReplaceEffectTest.java | 4 +-- .../test/commander/duel/MythUnboundTest.java | 1 - .../test/commander/duel/NorinTheWaryTest.java | 5 +-- .../CommanderReplacementEffect.java | 2 -- Mage/src/main/java/mage/game/GameImpl.java | 35 +++++++++++++++++++ Mage/src/main/java/mage/game/GameState.java | 10 ++++++ .../main/java/mage/players/PlayerImpl.java | 2 +- 8 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CastBRGCommanderTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CastBRGCommanderTest.java index 9abe4909fac..924781998f7 100644 --- a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CastBRGCommanderTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CastBRGCommanderTest.java @@ -160,7 +160,7 @@ public class CastBRGCommanderTest extends CardTestCommanderDuelBase { assertGraveyardCount(playerA, "Mogg Infestation", 1); assertCommandZoneCount(playerB, "Daxos of Meletis", 1); - assertPermanentCount(playerB, "Goblin", 0); + assertPermanentCount(playerB, "Goblin", 2); } diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderReplaceEffectTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderReplaceEffectTest.java index 4e7dcb3b2d3..1461804f897 100644 --- a/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderReplaceEffectTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/CommanderReplaceEffectTest.java @@ -110,8 +110,8 @@ public class CommanderReplaceEffectTest extends CardTestCommanderDuelBase { execute(); assertPermanentCount(playerA, "Soulherder", 1); - assertPermanentCount(playerA, "Daxos of Meletis", 0); - assertCommandZoneCount(playerA, "Daxos of Meletis", 1); + assertPermanentCount(playerA, "Daxos of Meletis", 1); + assertCommandZoneCount(playerA, "Daxos of Meletis", 0); assertPowerToughness(playerA, "Soulherder", 2, 2); } diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/MythUnboundTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/MythUnboundTest.java index b08652f2bb8..3375f7a23ca 100644 --- a/Mage.Tests/src/test/java/org/mage/test/commander/duel/MythUnboundTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/MythUnboundTest.java @@ -52,7 +52,6 @@ public class MythUnboundTest extends CardTestCommanderDuelBase { assertPermanentCount(playerA, "Myth Unbound", 1); assertGraveyardCount(playerB, "Lightning Bolt", 1); assertPermanentCount(playerA, "Oviya Pashiri, Sage Lifecrafter", 1); - assertHandCount(playerA, 1); assertTappedCount("Forest", false, 4 - 3); // 1 for first, 2 for second cast } } diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/NorinTheWaryTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/NorinTheWaryTest.java index 8db47a88fb1..36a44ea0a94 100644 --- a/Mage.Tests/src/test/java/org/mage/test/commander/duel/NorinTheWaryTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/NorinTheWaryTest.java @@ -72,7 +72,7 @@ public class NorinTheWaryTest extends CardTestCommanderDuelBase { } @Test - public void castNorinTheWaryToCommandAndReturn() { + public void castNorinTheWaryToCommandAndNotReturn() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.HAND, playerA, "Lightning Bolt", 1); @@ -83,8 +83,9 @@ public class NorinTheWaryTest extends CardTestCommanderDuelBase { execute(); assertGraveyardCount(playerA, "Lightning Bolt", 1); - assertPermanentCount(playerA, "Norin the Wary", 1); + assertPermanentCount(playerA, "Norin the Wary", 0); assertExileCount("Norin the Wary", 0); + assertCommandZoneCount(playerA, "Norin the Wary", 1); assertLife(playerB, 37); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/CommanderReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/CommanderReplacementEffect.java index e60ccca358d..5ccacd87115 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/CommanderReplacementEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/CommanderReplacementEffect.java @@ -106,8 +106,6 @@ public class CommanderReplacementEffect extends ReplacementEffectImpl { switch (zEvent.getToZone()) { case LIBRARY: case HAND: - case GRAVEYARD: - case EXILED: if (commanderId.equals(zEvent.getTargetId())) { return true; } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index a1f0d9e9cfa..f32b53881b7 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1853,6 +1853,41 @@ public abstract class GameImpl implements Game, Serializable { } } + // If a commander is in a graveyard or in exile and that card was put into that zone + // since the last time state-based actions were checked, its owner may put it into the command zone. + for (Player player : state.getPlayers().values()) { + Set commanderIds = getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER); + if (commanderIds.isEmpty()) { + continue; + } + Set commanders = new HashSet<>(); + Cards toMove = new CardsImpl(); + player.getGraveyard() + .stream() + .filter(commanderIds::contains) + .map(this::getCard) + .filter(Objects::nonNull) + .forEach(commanders::add); + commanderIds + .stream() + .map(uuid -> getExile().getCard(uuid, this)) + .filter(Objects::nonNull) + .forEach(commanders::add); + commanders.removeIf(card -> state.checkCommanderShouldStay(card, this)); + for (Card card : commanders) { + if (player.chooseUse(Outcome.Benefit, "Move " + card.getIdName() + " to the command zone or leave it in its current zone?", "You can only make this choice once", "Move to command", "Leave in current zone", null, this)) { + toMove.add(card); + } else { + state.setCommanderShouldStay(card, this); + } + } + if (toMove.isEmpty()) { + continue; + } + player.moveCards(toMove, Zone.COMMAND, null, this); + somethingHappened = true; + } + // 704.5e If a copy of a spell is in a zone other than the stack, it ceases to exist. If a copy of a card is in any zone other than the stack or the battlefield, it ceases to exist. // (Isochron Scepter) 12/1/2004: If you don't want to cast the copy, you can choose not to; the copy ceases to exist the next time state-based actions are checked. Iterator copiedCards = this.getState().getCopiedCards().iterator(); diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index dabfb437001..f156e307f58 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -5,6 +5,7 @@ import java.util.*; import static java.util.Collections.emptyList; import java.util.stream.Collectors; import mage.MageObject; +import mage.MageObjectReference; import mage.abilities.*; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffects; @@ -98,6 +99,7 @@ public class GameState implements Serializable, Copyable { private Map copiedCards = new HashMap<>(); private int permanentOrderNumber; private Map usePowerInsteadOfToughnessForDamageLethalityFilters = new HashMap<>(); + private final Set commandersToStay = new HashSet<>(); private int applyEffectsCounter; // Upcounting number of each applyEffects execution @@ -1225,4 +1227,12 @@ public class GameState implements Serializable, Copyable { .map(usePowerInsteadOfToughnessForDamageLethalityFilters::get) .collect(Collectors.toList()); } + + boolean checkCommanderShouldStay(Card card, Game game) { + return commandersToStay.stream().anyMatch(mor -> mor.refersTo(card, game)); + } + + void setCommanderShouldStay(Card card, Game game) { + commandersToStay.add(new MageObjectReference(card, game)); + } } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index a816624f03d..db4f20f6619 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -3879,7 +3879,7 @@ public abstract class PlayerImpl implements Player, Serializable { break; case COMMAND: for (Card card : cards) { - if (moveCardToCommandWithInfo(card, source.getSourceId(), game, fromZone)) { + if (moveCardToCommandWithInfo(card, source == null ? null : source.getSourceId(), game, fromZone)) { successfulMovedCards.add(card); } }