diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/conditional/ManaWasSpentToCastTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/ManaWasSpentToCastTest.java index 3dfa9cb1d3..e9d3a77c47 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/conditional/ManaWasSpentToCastTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/conditional/ManaWasSpentToCastTest.java @@ -240,7 +240,6 @@ public class ManaWasSpentToCastTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Sliver Construct", 1); } - @Ignore // currently fails @Test public void testManaDrainCopied() { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 11); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java index ecd470cc56..16a66f2dad 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java @@ -24,13 +24,15 @@ public class AdventureCardsTest extends CardTestPlayerBase { * Sorcery — Adventure * Create a Food token. */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.HAND, playerA, "Curious Pair"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, "Food", 1); assertExileCount(playerA, "Curious Pair", 1); @@ -61,14 +63,17 @@ public class AdventureCardsTest extends CardTestPlayerBase { @Test public void testCastCuriousPair() { - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.HAND, playerA, "Curious Pair"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, "Food", 0); assertPermanentCount(playerA, "Curious Pair", 1); @@ -78,16 +83,19 @@ public class AdventureCardsTest extends CardTestPlayerBase { @Test public void testCastTreatsToShareAndCuriousPair() { - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.HAND, playerA, "Curious Pair"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, "Food", 1); assertPermanentCount(playerA, "Curious Pair", 1); @@ -103,16 +111,19 @@ public class AdventureCardsTest extends CardTestPlayerBase { * Whenever you cast a creature spell that has an Adventure, draw a card. * 1/1 */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Edgewall Innkeeper"); addCard(Zone.HAND, playerA, "Curious Pair"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, "Food", 1); assertPermanentCount(playerA, "Curious Pair", 0); @@ -122,14 +133,18 @@ public class AdventureCardsTest extends CardTestPlayerBase { @Test public void testCastCuriousPairWithEdgewallInnkeeper() { - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Edgewall Innkeeper"); addCard(Zone.HAND, playerA, "Curious Pair"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); + assertAllCommandsUsed(); + assertHandCount(playerA, 1); assertPermanentCount(playerA, "Food", 0); assertPermanentCount(playerA, "Curious Pair", 1); @@ -139,17 +154,20 @@ public class AdventureCardsTest extends CardTestPlayerBase { @Test public void testCastTreatsToShareAndCuriousPairWithEdgewallInnkeeper() { - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Edgewall Innkeeper"); addCard(Zone.HAND, playerA, "Curious Pair"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertAllCommandsUsed(); + assertHandCount(playerA, 1); assertPermanentCount(playerA, "Food", 1); assertPermanentCount(playerA, "Curious Pair", 1); @@ -159,17 +177,17 @@ public class AdventureCardsTest extends CardTestPlayerBase { @Test public void testCastCuriousPairWithMysteriousPathlighter() { - setStrictChooseMode(true); - addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); addCard(Zone.BATTLEFIELD, playerA, "Mysterious Pathlighter"); addCard(Zone.HAND, playerA, "Curious Pair"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); + + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, "Food", 0); assertPermanentCount(playerA, "Curious Pair", 1); @@ -186,20 +204,22 @@ public class AdventureCardsTest extends CardTestPlayerBase { * Target opponent reveals their hand. You choose a nonland card from it. That player discards that card. * You may put a card that has an Adventure that player owns from exile into that player's graveyard. */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.HAND, playerA, "Curious Pair"); addCard(Zone.HAND, playerA, "Opt"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); - + // addCard(Zone.BATTLEFIELD, playerB, "Swamp"); addCard(Zone.BATTLEFIELD, playerB, "Swamp"); addCard(Zone.BATTLEFIELD, playerB, "Swamp"); addCard(Zone.HAND, playerB, "Memory Theft"); - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Memory Theft", playerA); - playerB.addChoice("Opt"); - playerB.addChoice("Curious Pair"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Memory Theft", playerA); + setChoice(playerB, "Opt"); + setChoice(playerB, "Curious Pair"); + + setStrictChooseMode(true); setStopAt(2, PhaseStep.BEGIN_COMBAT); execute(); assertAllCommandsUsed(); @@ -216,12 +236,13 @@ public class AdventureCardsTest extends CardTestPlayerBase { * Artifact * Whenever you cast an Adventure instant or sorcery spell, copy it. You may choose new targets for the copy. */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Lucky Clover"); addCard(Zone.HAND, playerA, "Curious Pair"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertAllCommandsUsed(); @@ -240,15 +261,16 @@ public class AdventureCardsTest extends CardTestPlayerBase { * Instant * Copy target instant or sorcery spell, except that the copy is red. You may choose new targets for the copy. */ - setStrictChooseMode(true); - addCard(Zone.BATTLEFIELD, playerA, "Forest"); - addCard(Zone.BATTLEFIELD, playerA, "Mountain"); - addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.HAND, playerA, "Curious Pair"); addCard(Zone.HAND, playerA, "Fork"); + + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fork", "Treats to Share"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertAllCommandsUsed(); @@ -269,15 +291,16 @@ public class AdventureCardsTest extends CardTestPlayerBase { * Instant * Counter target spell. */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerB, "Island"); addCard(Zone.BATTLEFIELD, playerB, "Island"); addCard(Zone.HAND, playerA, "Curious Pair"); addCard(Zone.HAND, playerB, "Counterspell"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Counterspell", "Treats to Share"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertAllCommandsUsed(); @@ -300,7 +323,6 @@ public class AdventureCardsTest extends CardTestPlayerBase { * Target opponent reveals their hand. You choose a nonland card from that player's graveyard or hand and exile it. * You may cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast that spell. */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Island", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1); addCard(Zone.BATTLEFIELD, playerA, "Forest", 6); @@ -313,10 +335,11 @@ public class AdventureCardsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertHandCount(playerB, 0); assertPermanentCount(playerB, 0); @@ -346,7 +369,6 @@ public class AdventureCardsTest extends CardTestPlayerBase { * Target creature gets +2/+0 until end of turn. */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6); addCard(Zone.BATTLEFIELD, playerA, "Eager Cadet"); addCard(Zone.HAND, playerA, "Rimrock Knight", 2); @@ -356,10 +378,11 @@ public class AdventureCardsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rimrock Knight"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rimrock Knight"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, "Rimrock Knight", 2); assertPermanentCount(playerA, "Eager Cadet", 1); @@ -370,16 +393,16 @@ public class AdventureCardsTest extends CardTestPlayerBase { @Test public void testRimrockKnightPermanentText() { - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); addCard(Zone.HAND, playerA, "Rimrock Knight"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rimrock Knight"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, "Rimrock Knight", 1); assertExileCount(playerA, 0); @@ -414,7 +437,6 @@ public class AdventureCardsTest extends CardTestPlayerBase { * Whenever you cast an instant or sorcery spell from your library, copy it. You may choose new targets for the copy. * 2/4 */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Melek, Izzet Paragon"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); removeAllCardsFromLibrary(playerA); @@ -422,10 +444,11 @@ public class AdventureCardsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, 4); assertPermanentCount(playerA, "Food", 2); @@ -465,7 +488,6 @@ public class AdventureCardsTest extends CardTestPlayerBase { * You may cast the top card of your library if it's a creature card. * 7/7 */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Garruk's Horde"); addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); removeAllCardsFromLibrary(playerA); @@ -473,10 +495,11 @@ public class AdventureCardsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, "Food", 0); assertPermanentCount(playerA, "Curious Pair", 1); @@ -516,7 +539,6 @@ public class AdventureCardsTest extends CardTestPlayerBase { * −7: You get an emblem with "Instant and sorcery cards in your graveyard have retrace." * Loyalty: 3 */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.BATTLEFIELD, playerA, "Wrenn and Six"); addCard(Zone.GRAVEYARD, playerA, "Curious Pair"); @@ -531,10 +553,11 @@ public class AdventureCardsTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); setChoice(playerA, "Forest"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); - assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, "Food", 1); assertPermanentCount(playerA, "Curious Pair", 0); @@ -555,7 +578,6 @@ public class AdventureCardsTest extends CardTestPlayerBase { * −3: Return up to one target artifact, creature, or enchantment to its owner's hand. Draw a card. * Loyalty: 4 */ - setStrictChooseMode(true); addCard(Zone.BATTLEFIELD, playerA, "Teferi, Time Raveler"); addCard(Zone.BATTLEFIELD, playerA, "Forest"); addCard(Zone.HAND, playerA, "Curious Pair"); @@ -565,10 +587,11 @@ public class AdventureCardsTest extends CardTestPlayerBase { // showAvailableAbilities("abils", 1, PhaseStep.BEGIN_COMBAT, playerA); castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Treats to Share"); + setStrictChooseMode(true); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); execute(); - assertAllCommandsUsed(); + assertHandCount(playerA, 0); assertPermanentCount(playerA, 3); assertPermanentCount(playerA, "Food", 1); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 0b4845b3a3..08cc84d46b 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -65,7 +65,6 @@ import mage.util.MessageToClient; import mage.util.RandomUtil; import mage.util.functions.CopyApplier; import mage.watchers.Watcher; -import mage.watchers.Watchers; import mage.watchers.common.*; import org.apache.log4j.Logger; @@ -2051,6 +2050,7 @@ public abstract class GameImpl implements Game, Serializable { // to exist the next time state-based actions are checked. // // Copied cards can be stored in GameState.copiedCards or in game state value (until LKI rework) + // Copied cards list contains all parts of split/adventure/mdfc Set allCopiedCards = new HashSet<>(); allCopiedCards.addAll(this.getState().getCopiedCards()); Map stateSavedCopiedCards = this.getState().getValues(GameState.COPIED_CARD_KEY); @@ -2060,6 +2060,7 @@ public abstract class GameImpl implements Game, Serializable { .filter(Objects::nonNull) .collect(Collectors.toList()) ); + Set copiedCardsToRemove = new HashSet<>(); for (Card copiedCard : allCopiedCards) { // 1. Zone must be checked from main card only cause mdf parts can have different zones // (one side on battlefield, another side on outside) @@ -2070,13 +2071,25 @@ public abstract class GameImpl implements Game, Serializable { Zone zone = state.getZone(copiedCard.getMainCard().getId()); // TODO: remember LKI of copied cards here after LKI rework switch (zone) { - case BATTLEFIELD: + case OUTSIDE: + case BATTLEFIELD: { + // keep in battlefield + // keep in outside (it's a final zone for all copied cards) continue; - case STACK: - if (getStack().getStackObject(copiedCard.getId()) != null) { + } + + case STACK: { + // copied cards aren't moves and keeps in Stack zone after resolve, + // so it must be moved manually as SBA (see Outside zone change at the end) + MageObject object = getStack().getStackObject(copiedCard.getId()); + if (object != null) { + // keep in stack until resolve continue; } - case GRAVEYARD: + break; + } + + case GRAVEYARD: { for (Player player : getPlayers().values()) { if (player.getGraveyard().contains(copiedCard.getId())) { player.getGraveyard().remove(copiedCard); @@ -2084,7 +2097,9 @@ public abstract class GameImpl implements Game, Serializable { } } break; - case HAND: + } + + case HAND: { for (Player player : getPlayers().values()) { if (player.getHand().contains(copiedCard.getId())) { player.getHand().remove(copiedCard); @@ -2092,7 +2107,9 @@ public abstract class GameImpl implements Game, Serializable { } } break; - case LIBRARY: + } + + case LIBRARY: { for (Player player : getPlayers().values()) { if (player.getLibrary().getCard(copiedCard.getId(), this) != null) { player.getLibrary().remove(copiedCard.getId(), this); @@ -2100,15 +2117,30 @@ public abstract class GameImpl implements Game, Serializable { } } break; - case EXILED: + } + + case EXILED: { getExile().removeCard(copiedCard, this); break; + } + + case COMMAND: + default: { + break; + } } - // remove copied card info - this.getState().getCopiedCards().remove(copiedCard); - this.getState().removeValue(GameState.COPIED_CARD_KEY + copiedCard.getId().toString()); + // copied card can be removed to Outside + copiedCardsToRemove.add(copiedCard); } + // real remove + copiedCardsToRemove.forEach(card -> { + card.setZone(Zone.OUTSIDE, this); + this.getState().getCopiedCards().remove(card); + // must keep card in game state as LKI alternative until LKI rework, so don't remove from it + // TODO: change after LKI rework + //this.getState().removeValue(GameState.COPIED_CARD_KEY + copiedCard.getId().toString()); + }); List legendary = new ArrayList<>(); List worldEnchantment = new ArrayList<>(); diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 4a25f489c8..4ede3d39fe 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -233,7 +233,10 @@ public class Spell extends StackObjectImpl implements Card { } } if (game.getState().getZone(card.getMainCard().getId()) == Zone.STACK) { - if (!isCopy()) { + if (isCopy()) { + // copied spell, only remove from stack + game.getStack().remove(this, game); + } else { controller.moveCards(card, Zone.GRAVEYARD, ability, game); } } @@ -438,7 +441,7 @@ public class Spell extends StackObjectImpl implements Card { } } } else { - // Copied spell, only remove from stack + // copied spell, only remove from stack game.getStack().remove(this, game); } } @@ -847,6 +850,7 @@ public class Spell extends StackObjectImpl implements Card { @Override public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List appliedEffects) { if (this.isCopy()) { + // copied spell, only remove from stack game.getStack().remove(this, game); return true; } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index a9ffa80978..a6cd7eed8b 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -4472,7 +4472,7 @@ public abstract class PlayerImpl implements Player, Serializable { } else if (card instanceof Spell) { final Spell spell = (Spell) card; if (spell.isCopy()) { - // Copied spell, only remove from stack + // copied spell, only remove from stack game.getStack().remove(spell, game); } }