From e95ae2675bbf9a906a8bec7959bfe83a753907aa Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 4 Jun 2020 23:47:50 +0400 Subject: [PATCH] Fixed different ZCC in split card's parts (flashback fix, see 95075cf33edb095437b7e44a6ced7883c9cd9355); Improve moveToZone code and fixed some cards with wrong commands queue (e.g. directly removes card from zone and then calls moveToZone again); --- Mage.Sets/src/mage/cards/c/ChandraAblaze.java | 1 - Mage.Sets/src/mage/cards/d/DarkRevenant.java | 1 - .../src/mage/cards/d/DwellOnThePast.java | 1 - Mage.Sets/src/mage/cards/g/GaeasBlessing.java | 1 - .../src/mage/cards/g/GraveyardShovel.java | 1 - .../src/mage/cards/k/KrosanReclamation.java | 1 - .../src/mage/cards/m/MemorysJourney.java | 1 - .../src/mage/cards/s/SereneRemembrance.java | 1 - .../src/mage/cards/s/SpellweaverVolute.java | 2 +- .../mage/cards/s/StreamOfConsciousness.java | 1 - Mage.Sets/src/mage/cards/u/UndyingBeast.java | 1 - .../CastSplitCardsWithFlashbackTest.java | 37 +++++++++++++++++++ .../base/impl/CardTestPlayerAPIImpl.java | 5 +-- .../common/PutOnLibrarySourceEffect.java | 1 - .../main/java/mage/cards/AdventureCard.java | 22 ++++++++++- Mage/src/main/java/mage/cards/CardImpl.java | 6 ++- Mage/src/main/java/mage/cards/Cards.java | 2 +- Mage/src/main/java/mage/cards/CardsImpl.java | 6 +-- Mage/src/main/java/mage/cards/MeldCard.java | 22 +++++++++-- Mage/src/main/java/mage/cards/SplitCard.java | 34 ++++++++++++----- .../main/java/mage/players/PlayerImpl.java | 5 ++- 21 files changed, 116 insertions(+), 36 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/ChandraAblaze.java b/Mage.Sets/src/mage/cards/c/ChandraAblaze.java index 803758c7035..4128d0687f0 100644 --- a/Mage.Sets/src/mage/cards/c/ChandraAblaze.java +++ b/Mage.Sets/src/mage/cards/c/ChandraAblaze.java @@ -172,7 +172,6 @@ class ChandraAblazeEffect5 extends OneShotEffect { Card card = game.getCard(target.getFirstTarget()); if (card != null) { player.cast(card.getSpellAbility(), game, true, new MageObjectReference(source.getSourceObject(game), game)); - player.getGraveyard().remove(card); cards.remove(card); } } diff --git a/Mage.Sets/src/mage/cards/d/DarkRevenant.java b/Mage.Sets/src/mage/cards/d/DarkRevenant.java index 6741b40a2bd..8f42634f7ee 100644 --- a/Mage.Sets/src/mage/cards/d/DarkRevenant.java +++ b/Mage.Sets/src/mage/cards/d/DarkRevenant.java @@ -69,7 +69,6 @@ class DarkRevenantEffect extends OneShotEffect { if (card != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { Player owner = game.getPlayer(card.getOwnerId()); if(owner != null) { - owner.getGraveyard().remove(card); return card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); } } diff --git a/Mage.Sets/src/mage/cards/d/DwellOnThePast.java b/Mage.Sets/src/mage/cards/d/DwellOnThePast.java index efa62fddb7e..57164964d99 100644 --- a/Mage.Sets/src/mage/cards/d/DwellOnThePast.java +++ b/Mage.Sets/src/mage/cards/d/DwellOnThePast.java @@ -69,7 +69,6 @@ class DwellOnThePastEffect extends OneShotEffect { Card card = game.getCard(targetId); if (card != null) { if (player.getGraveyard().contains(card.getId())) { - player.getGraveyard().remove(card); card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); shuffle = true; } diff --git a/Mage.Sets/src/mage/cards/g/GaeasBlessing.java b/Mage.Sets/src/mage/cards/g/GaeasBlessing.java index 62b9516c83c..8f617b4bc23 100644 --- a/Mage.Sets/src/mage/cards/g/GaeasBlessing.java +++ b/Mage.Sets/src/mage/cards/g/GaeasBlessing.java @@ -76,7 +76,6 @@ class GaeasBlessingEffect extends OneShotEffect { Card card = game.getCard(targetId); if (card != null) { if (player.getGraveyard().contains(card.getId())) { - player.getGraveyard().remove(card); card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); shuffle = true; } diff --git a/Mage.Sets/src/mage/cards/g/GraveyardShovel.java b/Mage.Sets/src/mage/cards/g/GraveyardShovel.java index b19130c100f..9e348846c11 100644 --- a/Mage.Sets/src/mage/cards/g/GraveyardShovel.java +++ b/Mage.Sets/src/mage/cards/g/GraveyardShovel.java @@ -69,7 +69,6 @@ class GraveyardShovelEffect extends OneShotEffect { if (targetPlayer.chooseTarget(Outcome.Exile, target, source, game)) { Card card = game.getCard(target.getFirstTarget()); if (card != null) { - targetPlayer.getGraveyard().remove(card); card.moveToExile(null, "", source.getSourceId(), game); if (card.isCreature()) { controller.gainLife(2, game, source); diff --git a/Mage.Sets/src/mage/cards/k/KrosanReclamation.java b/Mage.Sets/src/mage/cards/k/KrosanReclamation.java index 73e848177cb..2ae901a246a 100644 --- a/Mage.Sets/src/mage/cards/k/KrosanReclamation.java +++ b/Mage.Sets/src/mage/cards/k/KrosanReclamation.java @@ -74,7 +74,6 @@ class KrosanReclamationEffect extends OneShotEffect { Card card = game.getCard(targetId); if (card != null) { if (player.getGraveyard().contains(card.getId())) { - player.getGraveyard().remove(card); card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); shuffle = true; } diff --git a/Mage.Sets/src/mage/cards/m/MemorysJourney.java b/Mage.Sets/src/mage/cards/m/MemorysJourney.java index a54963a8037..71b1f58fc37 100644 --- a/Mage.Sets/src/mage/cards/m/MemorysJourney.java +++ b/Mage.Sets/src/mage/cards/m/MemorysJourney.java @@ -73,7 +73,6 @@ class MemorysJourneyEffect extends OneShotEffect { Card card = game.getCard(targetId); if (card != null) { if (player.getGraveyard().contains(card.getId())) { - player.getGraveyard().remove(card); card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); shuffle = true; } diff --git a/Mage.Sets/src/mage/cards/s/SereneRemembrance.java b/Mage.Sets/src/mage/cards/s/SereneRemembrance.java index b805b2f3b54..441fd9d807e 100644 --- a/Mage.Sets/src/mage/cards/s/SereneRemembrance.java +++ b/Mage.Sets/src/mage/cards/s/SereneRemembrance.java @@ -68,7 +68,6 @@ class SereneRemembranceEffect extends OneShotEffect { for (Player player : game.getPlayers().values()) { if (player.getGraveyard().contains(card.getId())) { graveyardPlayer = player; - player.getGraveyard().remove(card); result |= card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); } } diff --git a/Mage.Sets/src/mage/cards/s/SpellweaverVolute.java b/Mage.Sets/src/mage/cards/s/SpellweaverVolute.java index 971f423f3cf..7f6b4fd2d0a 100644 --- a/Mage.Sets/src/mage/cards/s/SpellweaverVolute.java +++ b/Mage.Sets/src/mage/cards/s/SpellweaverVolute.java @@ -90,7 +90,7 @@ class SpellweaverVoluteEffect extends OneShotEffect { && controller.chooseUse(Outcome.Copy, "Create a copy of " + enchantedCard.getName() + '?', source, game)) { Card copiedCard = game.copyCard(enchantedCard, source, source.getControllerId()); if (copiedCard != null) { - ownerEnchanted.getGraveyard().add(copiedCard); + controller.getGraveyard().add(copiedCard); game.getState().setZone(copiedCard.getId(), Zone.GRAVEYARD); if (controller.chooseUse(Outcome.PlayForFree, "Cast the copied card without paying mana cost?", source, game)) { if (copiedCard.getSpellAbility() != null) { diff --git a/Mage.Sets/src/mage/cards/s/StreamOfConsciousness.java b/Mage.Sets/src/mage/cards/s/StreamOfConsciousness.java index 24ac6e0dc4d..ed7fd756ed5 100644 --- a/Mage.Sets/src/mage/cards/s/StreamOfConsciousness.java +++ b/Mage.Sets/src/mage/cards/s/StreamOfConsciousness.java @@ -72,7 +72,6 @@ class StreamOfConsciousnessEffect extends OneShotEffect { Card card = game.getCard(targetId); if (card != null) { if (player.getGraveyard().contains(card.getId())) { - player.getGraveyard().remove(card); card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); shuffle = true; } diff --git a/Mage.Sets/src/mage/cards/u/UndyingBeast.java b/Mage.Sets/src/mage/cards/u/UndyingBeast.java index 20bf25c5a6e..8dad549bf4d 100644 --- a/Mage.Sets/src/mage/cards/u/UndyingBeast.java +++ b/Mage.Sets/src/mage/cards/u/UndyingBeast.java @@ -64,7 +64,6 @@ class UndyingBeastEffect extends OneShotEffect { if (card != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { Player owner = game.getPlayer(card.getOwnerId()); if(owner != null) { - owner.getGraveyard().remove(card); return card.moveToZone(Zone.LIBRARY, source.getSourceId(), game, true); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithFlashbackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithFlashbackTest.java index db85f76f92f..9c68cd0f05d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithFlashbackTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/split/CastSplitCardsWithFlashbackTest.java @@ -70,6 +70,43 @@ public class CastSplitCardsWithFlashbackTest extends CardTestPlayerBase { assertExileCount(playerA, "Wear // Tear", 1); } + @Test + public void test_Flashback_SplitLeft_ZCCChanged() { + // {1}{U} + // When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn. + addCard(Zone.HAND, playerA, "Snapcaster Mage", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + // + // Wear {1}{R} Destroy target artifact. + // Tear {W} Destroy target enchantment. + addCard(Zone.HAND, playerA, "Wear // Tear", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2 + 2); // for first Wear cast + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerB, "Bident of Thassa", 1); // Legendary Enchantment Artifact + addCard(Zone.BATTLEFIELD, playerB, "Bow of Nylea", 1); // Legendary Enchantment Artifact + + // play card as normal to change ZCC for split cards (simulate GUI session - ZCC's was bugged and card's parts was able to have different ZCC) + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wear", "Bow of Nylea"); + + // add flashback + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage"); + addTarget(playerA, "Wear // Tear"); + + // cast as flashback + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback {1}{R}", "Bident of Thassa"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerB, "Bident of Thassa", 1); + assertGraveyardCount(playerB, "Bow of Nylea", 1); + assertExileCount(playerA, "Wear // Tear", 1); + } + @Test public void test_Flashback_SplitRight() { // {1}{U} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index 6a7250afeec..5cc971d404e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -1601,13 +1601,12 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } /** - * * @param turnNum * @param step * @param player * @param ability - * @param targetName use NO_TARGET if there is no target to set - * @param spellOnStack + * @param targetName use NO_TARGET if there is no target to set + * @param spellOnStack */ public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, String targetName, String spellOnStack) { // TODO: it's uses computerPlayer to execute, only ability target will work, but choices and targets commands aren't diff --git a/Mage/src/main/java/mage/abilities/effects/common/PutOnLibrarySourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PutOnLibrarySourceEffect.java index cd7f43bddb8..28e5b10ba54 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PutOnLibrarySourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PutOnLibrarySourceEffect.java @@ -54,7 +54,6 @@ public class PutOnLibrarySourceEffect extends OneShotEffect { } else if (sourceObject instanceof Card && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { for (Player player : game.getPlayers().values()) { if (player.getGraveyard().contains(sourceObject.getId())) { - player.getGraveyard().remove(((Card) sourceObject)); ((Card) sourceObject).moveToZone(Zone.LIBRARY, source.getSourceId(), game, onTop); return true; } diff --git a/Mage/src/main/java/mage/cards/AdventureCard.java b/Mage/src/main/java/mage/cards/AdventureCard.java index 1cb62d410ac..1fc792311e9 100644 --- a/Mage/src/main/java/mage/cards/AdventureCard.java +++ b/Mage/src/main/java/mage/cards/AdventureCard.java @@ -1,7 +1,5 @@ package mage.cards; -import java.util.List; -import java.util.UUID; import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; @@ -9,6 +7,10 @@ import mage.abilities.SpellAbility; import mage.constants.CardType; import mage.constants.Zone; import mage.game.Game; +import mage.game.events.ZoneChangeEvent; + +import java.util.List; +import java.util.UUID; /** * @author TheElk801 @@ -64,6 +66,22 @@ public abstract class AdventureCard extends CardImpl { return false; } + @Override + public boolean removeFromZone(Game game, Zone fromZone, UUID sourceId) { + // zone contains only one main card + return super.removeFromZone(game, fromZone, sourceId); + } + + @Override + public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { + if (isCopy()) { // same as meld cards + super.updateZoneChangeCounter(game, event); + return; + } + super.updateZoneChangeCounter(game, event); + getSpellCard().updateZoneChangeCounter(game, event); + } + @Override public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { switch (ability.getSpellAbilityType()) { diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index b968fbc342e..b9a1b37f0e7 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -523,7 +523,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card { case EXILED: if (game.getExile().getCard(getId(), game) != null) { removed = game.getExile().removeCard(this, game); - } break; case STACK: @@ -533,15 +532,18 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } else { stackObject = game.getStack().getSpell(this.getId(), false); } + if (stackObject == null && (this instanceof SplitCard)) { // handle if half of Split cast is on the stack stackObject = game.getStack().getSpell(((SplitCard) this).getLeftHalfCard().getId(), false); if (stackObject == null) { stackObject = game.getStack().getSpell(((SplitCard) this).getRightHalfCard().getId(), false); } } + if (stackObject == null && (this instanceof AdventureCard)) { stackObject = game.getStack().getSpell(((AdventureCard) this).getSpellCard().getId(), false); } + if (stackObject == null) { stackObject = game.getStack().getSpell(getId(), false); } @@ -589,6 +591,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } } else { logger.warn("Couldn't find card in fromZone, card=" + getIdName() + ", fromZone=" + fromZone); + // possible reason: you to remove card from wrong zone or card already removed, + // e.g. you added copy card to wrong graveyard (see owner) or removed card from graveyard before moveToZone call } return removed; } diff --git a/Mage/src/main/java/mage/cards/Cards.java b/Mage/src/main/java/mage/cards/Cards.java index 4ffe0c228ff..a6c6a21efcd 100644 --- a/Mage/src/main/java/mage/cards/Cards.java +++ b/Mage/src/main/java/mage/cards/Cards.java @@ -15,7 +15,7 @@ public interface Cards extends Set, Serializable { Card get(UUID cardId, Game game); - void remove(Card card); + boolean remove(Card card); void setOwner(UUID ownerId, Game game); diff --git a/Mage/src/main/java/mage/cards/CardsImpl.java b/Mage/src/main/java/mage/cards/CardsImpl.java index 250bb3f3b84..ed69e8ea2f6 100644 --- a/Mage/src/main/java/mage/cards/CardsImpl.java +++ b/Mage/src/main/java/mage/cards/CardsImpl.java @@ -64,11 +64,11 @@ public class CardsImpl extends LinkedHashSet implements Cards, Serializabl } @Override - public void remove(Card card) { + public boolean remove(Card card) { if (card == null) { - return; + return false; } - this.remove(card.getId()); + return this.remove(card.getId()); } @Override diff --git a/Mage/src/main/java/mage/cards/MeldCard.java b/Mage/src/main/java/mage/cards/MeldCard.java index af29691a26e..6112d24e0f6 100644 --- a/Mage/src/main/java/mage/cards/MeldCard.java +++ b/Mage/src/main/java/mage/cards/MeldCard.java @@ -1,4 +1,3 @@ - package mage.cards; import mage.abilities.Ability; @@ -13,7 +12,6 @@ import java.util.List; import java.util.UUID; /** - * * @author emerald000 */ public abstract class MeldCard extends CardImpl { @@ -142,6 +140,24 @@ public abstract class MeldCard extends CardImpl { return value; } + @Override + public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, List appliedEffects) { + // TODO: missing override method for meld cards? See removeFromZone, updateZoneChangeCounter, etc + return super.moveToZone(toZone, sourceId, game, flag, appliedEffects); + } + + @Override + public void setZone(Zone zone, Game game) { + // TODO: missing override method for meld cards? See removeFromZone, updateZoneChangeCounter, etc + super.setZone(zone, game); + } + + @Override + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List appliedEffects) { + // TODO: missing override method for meld cards? See removeFromZone, updateZoneChangeCounter, etc + return super.moveToExile(exileId, name, sourceId, game, appliedEffects); + } + @Override public boolean removeFromZone(Game game, Zone fromZone, UUID sourceId) { if (isCopy()) { @@ -170,7 +186,7 @@ public abstract class MeldCard extends CardImpl { super.updateZoneChangeCounter(game, event); return; } - game.getState().updateZoneChangeCounter(objectId); + super.updateZoneChangeCounter(game, event); if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game) && halves.contains(topHalfCard.getId())) { topHalfCard.updateZoneChangeCounter(game, event); diff --git a/Mage/src/main/java/mage/cards/SplitCard.java b/Mage/src/main/java/mage/cards/SplitCard.java index 4205f051584..936339bf640 100644 --- a/Mage/src/main/java/mage/cards/SplitCard.java +++ b/Mage/src/main/java/mage/cards/SplitCard.java @@ -9,6 +9,7 @@ import mage.constants.CardType; import mage.constants.SpellAbilityType; import mage.constants.Zone; import mage.game.Game; +import mage.game.events.ZoneChangeEvent; import java.util.ArrayList; import java.util.List; @@ -74,6 +75,13 @@ public abstract class SplitCard extends CardImpl { return false; } + @Override + public void setZone(Zone zone, Game game) { + super.setZone(zone, game); + game.setZone(getLeftHalfCard().getId(), zone); + game.setZone(getRightHalfCard().getId(), zone); + } + @Override public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List appliedEffects) { if (super.moveToExile(exileId, name, sourceId, game, appliedEffects)) { @@ -85,6 +93,23 @@ public abstract class SplitCard extends CardImpl { return false; } + @Override + public boolean removeFromZone(Game game, Zone fromZone, UUID sourceId) { + // zone contains only one main card + return super.removeFromZone(game, fromZone, sourceId); + } + + @Override + public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) { + if (isCopy()) { // same as meld cards + super.updateZoneChangeCounter(game, event); + return; + } + super.updateZoneChangeCounter(game, event); + getLeftHalfCard().updateZoneChangeCounter(game, event); + getRightHalfCard().updateZoneChangeCounter(game, event); + } + @Override public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { switch (ability.getSpellAbilityType()) { @@ -99,13 +124,6 @@ public abstract class SplitCard extends CardImpl { } } - @Override - public void setZone(Zone zone, Game game) { - super.setZone(zone, game); - game.setZone(getLeftHalfCard().getId(), zone); - game.setZone(getRightHalfCard().getId(), zone); - } - @Override public Abilities getAbilities() { Abilities allAbilites = new AbilitiesImpl<>(); @@ -168,7 +186,5 @@ public abstract class SplitCard extends CardImpl { leftHalfCard.setOwnerId(ownerId); rightHalfCard.getAbilities().setControllerId(ownerId); rightHalfCard.setOwnerId(ownerId); - } - } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 8957b6905a4..95d6c8b320b 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -691,6 +691,8 @@ public abstract class PlayerImpl implements Player, Serializable { return false; } library.remove(card.getId(), game); + // must return true all the time (some cards can be removed directly from library, see getLibrary().removeFromTop) + // TODO: replace removeFromTop logic to normal with moveToZone return true; } @@ -919,8 +921,7 @@ public abstract class PlayerImpl implements Player, Serializable { @Override public boolean removeFromGraveyard(Card card, Game game) { - this.graveyard.remove(card); - return true; + return this.graveyard.remove(card); } @Override