From db3c2e9d8c11a5c20ff9efc626ed30f893d77bef Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Mon, 5 Sep 2016 09:51:32 -0400 Subject: [PATCH 1/7] Clean up an inconsistency in ZoneChangeEvent handling Some places set a bit on the ZoneChangeEvent if a permanent was meant to enter the battlefield tapped but only MeldCard ever read that bit to determine whether things should come into play tapped. --- Mage/src/main/java/mage/cards/CardImpl.java | 2 +- Mage/src/main/java/mage/cards/MeldCard.java | 2 +- Mage/src/main/java/mage/constants/Zone.java | 2 +- .../main/java/mage/game/events/ZoneChangeEvent.java | 10 ---------- Mage/src/main/java/mage/players/PlayerImpl.java | 2 +- 5 files changed, 4 insertions(+), 14 deletions(-) diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index ebe0e0aa52c..dd671cb3fb3 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -461,7 +461,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { @Override public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown, ArrayList appliedEffects) { - ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, controllerId, fromZone, Zone.BATTLEFIELD, appliedEffects, tapped); + ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, controllerId, fromZone, Zone.BATTLEFIELD, appliedEffects); if (facedown) { this.setFaceDown(true, game); } diff --git a/Mage/src/main/java/mage/cards/MeldCard.java b/Mage/src/main/java/mage/cards/MeldCard.java index d7ccc2d6578..0fb696ae937 100644 --- a/Mage/src/main/java/mage/cards/MeldCard.java +++ b/Mage/src/main/java/mage/cards/MeldCard.java @@ -279,7 +279,7 @@ public abstract class MeldCard extends CardImpl { game.setScopeRelevant(false); game.applyEffects(); if (entered) { - if (event.getFlag()) { + if (tapped) { permanent.setTapped(true); } event.setTarget(permanent); diff --git a/Mage/src/main/java/mage/constants/Zone.java b/Mage/src/main/java/mage/constants/Zone.java index 4acb086eec7..f9718238001 100644 --- a/Mage/src/main/java/mage/constants/Zone.java +++ b/Mage/src/main/java/mage/constants/Zone.java @@ -44,7 +44,7 @@ public enum Zone { if (this.equals(EXILED)) { return "exile zone"; } - return super.toString(); //To change body of generated methods, choose Tools | Templates. + return super.toString(); } } diff --git a/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java b/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java index e200e22652c..f4074354e86 100644 --- a/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java +++ b/Mage/src/main/java/mage/game/events/ZoneChangeEvent.java @@ -67,17 +67,12 @@ public class ZoneChangeEvent extends GameEvent { } public ZoneChangeEvent(UUID targetId, UUID sourceId, UUID playerId, Zone fromZone, Zone toZone, ArrayList appliedEffects) { - this(targetId, sourceId, playerId, fromZone, toZone, appliedEffects, false); - } - - public ZoneChangeEvent(UUID targetId, UUID sourceId, UUID playerId, Zone fromZone, Zone toZone, ArrayList appliedEffects, boolean comesIntoPlayTapped) { super(EventType.ZONE_CHANGE, targetId, sourceId, playerId); this.fromZone = fromZone; this.toZone = toZone; if (appliedEffects != null) { this.appliedEffects = appliedEffects; } - this.flag = comesIntoPlayTapped; } public ZoneChangeEvent(Permanent target, UUID playerId, Zone fromZone, Zone toZone) { @@ -111,9 +106,4 @@ public class ZoneChangeEvent extends GameEvent { public boolean isDiesEvent() { return (toZone == Zone.GRAVEYARD && fromZone == Zone.BATTLEFIELD); } - - public boolean comesIntoPlayTapped() { - return this.flag; - } - } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index d921af4bd41..51c0be590fb 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -3179,7 +3179,7 @@ public abstract class PlayerImpl implements Player, Serializable { if (faceDown) { card.setFaceDown(true, game); } - ZoneChangeEvent event = new ZoneChangeEvent(card.getId(), source.getSourceId(), controllingPlayerId, fromZone, Zone.BATTLEFIELD, appliedEffects, tapped); + ZoneChangeEvent event = new ZoneChangeEvent(card.getId(), source.getSourceId(), controllingPlayerId, fromZone, Zone.BATTLEFIELD, appliedEffects); if (!game.replaceEvent(event)) { // get permanent Permanent permanent; From 34846170c40cb19628c03b982c479f3b2948fd0f Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Mon, 5 Sep 2016 11:47:52 -0400 Subject: [PATCH 2/7] Remove an unused argument to Player.putInGraveyard --- .../test/java/org/mage/test/player/TestPlayer.java | 4 ++-- .../src/test/java/org/mage/test/stub/PlayerStub.java | 2 +- Mage/src/main/java/mage/cards/CardImpl.java | 2 +- Mage/src/main/java/mage/cards/MeldCard.java | 12 ++++-------- .../main/java/mage/game/permanent/PermanentCard.java | 4 +++- .../main/java/mage/game/permanent/PermanentMeld.java | 8 ++++---- Mage/src/main/java/mage/players/Player.java | 2 +- Mage/src/main/java/mage/players/PlayerImpl.java | 4 ++-- 8 files changed, 18 insertions(+), 20 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 62e89e3e74f..5a5270969e9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1239,8 +1239,8 @@ public class TestPlayer implements Player { } @Override - public boolean putInGraveyard(Card card, Game game, boolean fromBattlefield) { - return computerPlayer.putInGraveyard(card, game, fromBattlefield); + public boolean putInGraveyard(Card card, Game game) { + return computerPlayer.putInGraveyard(card, game); } @Override diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index dbc17018ca1..533e31dd00c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -594,7 +594,7 @@ public class PlayerStub implements Player { } @Override - public boolean putInGraveyard(Card card, Game game, boolean fromBattlefield) { + public boolean putInGraveyard(Card card, Game game) { return false; } diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index dd671cb3fb3..9b12c761f29 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -350,7 +350,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { updateZoneChangeCounter(game); switch (event.getToZone()) { case GRAVEYARD: - game.getPlayer(ownerId).putInGraveyard(this, game, !flag); + game.getPlayer(ownerId).putInGraveyard(this, game); break; case HAND: game.getPlayer(ownerId).getHand().add(this); diff --git a/Mage/src/main/java/mage/cards/MeldCard.java b/Mage/src/main/java/mage/cards/MeldCard.java index 0fb696ae937..952b60122bc 100644 --- a/Mage/src/main/java/mage/cards/MeldCard.java +++ b/Mage/src/main/java/mage/cards/MeldCard.java @@ -32,10 +32,6 @@ import java.util.UUID; import mage.constants.CardType; import mage.constants.Rarity; import mage.constants.Zone; -import static mage.constants.Zone.EXILED; -import static mage.constants.Zone.GRAVEYARD; -import static mage.constants.Zone.HAND; -import static mage.constants.Zone.LIBRARY; import mage.counters.Counter; import mage.game.Game; import mage.game.events.ZoneChangeEvent; @@ -136,8 +132,8 @@ public abstract class MeldCard extends CardImpl { updateZoneChangeCounter(game); switch (event.getToZone()) { case GRAVEYARD: - game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game, true); - game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game, true); + game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game); + game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game); break; case HAND: game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); @@ -202,8 +198,8 @@ public abstract class MeldCard extends CardImpl { updateZoneChangeCounter(game); switch (event.getToZone()) { case GRAVEYARD: - game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game, true); - game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game, true); + game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game); + game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game); break; case HAND: game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index bb583cd2921..269cb3dab06 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -38,6 +38,8 @@ import mage.cards.Card; import mage.cards.LevelerCard; import mage.constants.Zone; import mage.game.Game; +import mage.game.ZoneInfo; +import mage.game.ZonesHandler; import mage.game.command.Commander; import mage.game.events.ZoneChangeEvent; import mage.players.Player; @@ -164,7 +166,7 @@ public class PermanentCard extends PermanentImpl { card.updateZoneChangeCounter(game); switch (event.getToZone()) { case GRAVEYARD: - owner.putInGraveyard(card, game, !flag); + owner.putInGraveyard(card, game); break; case HAND: owner.getHand().add(card); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentMeld.java b/Mage/src/main/java/mage/game/permanent/PermanentMeld.java index 6bd0f13116a..ed649a0d1b8 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentMeld.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentMeld.java @@ -69,8 +69,8 @@ public class PermanentMeld extends PermanentCard { Card bottomHalfCard = meldCard.getBottomHalfCard(); switch (event.getToZone()) { case GRAVEYARD: - game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game, true); - game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game, true); + game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game); + game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game); break; case HAND: game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); @@ -118,8 +118,8 @@ public class PermanentMeld extends PermanentCard { Card bottomHalfCard = meldCard.getBottomHalfCard(); switch (event.getToZone()) { case GRAVEYARD: - game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game, true); - game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game, true); + game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game); + game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game); break; case HAND: game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index f476c8c5a3f..8c7af146c48 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -354,7 +354,7 @@ public interface Player extends MageItem, Copyable { boolean removeFromBattlefield(Permanent permanent, Game game); - boolean putInGraveyard(Card card, Game game, boolean fromBattlefield); + boolean putInGraveyard(Card card, Game game); boolean removeFromGraveyard(Card card, Game game); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 51c0be590fb..8f716fcd9db 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -853,11 +853,11 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public boolean putInGraveyard(Card card, Game game, boolean fromBattlefield) { + public boolean putInGraveyard(Card card, Game game) { if (card.getOwnerId().equals(playerId)) { this.graveyard.add(card); } else { - return game.getPlayer(card.getOwnerId()).putInGraveyard(card, game, fromBattlefield); + return game.getPlayer(card.getOwnerId()).putInGraveyard(card, game); } return true; } From eb89781154954fa6fb1326d04c3b104213f83652 Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Mon, 5 Sep 2016 22:17:04 -0400 Subject: [PATCH 3/7] Don't check for event validity in Karn's Ultimate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Karn Liberated should not check whether it’s valid before putting the Commanders where they belong. Technically any operations after Karn’s ultimate goes off are invalid if strict checks are made. --- Mage.Sets/src/mage/sets/newphyrexia/KarnLiberated.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/newphyrexia/KarnLiberated.java b/Mage.Sets/src/mage/sets/newphyrexia/KarnLiberated.java index 45c585dae9f..50a8851d98b 100644 --- a/Mage.Sets/src/mage/sets/newphyrexia/KarnLiberated.java +++ b/Mage.Sets/src/mage/sets/newphyrexia/KarnLiberated.java @@ -49,6 +49,7 @@ import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.ExileZone; import mage.game.Game; +import mage.game.command.Commander; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; @@ -140,7 +141,8 @@ class KarnLiberatedEffect extends OneShotEffect { && !player.getSideboard().contains(card.getId()) && !cards.contains(card)) { // not the exiled cards if (card.getId().equals(player.getCommanderId())) { - card.moveToZone(Zone.COMMAND, null, game, true); + game.addCommander(new Commander(card)); + game.setZone(card.getId(), Zone.COMMAND); } else { player.getLibrary().putOnTop(card, game); } From b87f91fd9717e0b2b126b546265103645cf2e7af Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Wed, 7 Sep 2016 21:19:36 -0400 Subject: [PATCH 4/7] Fix a Typo --- .../main/java/mage/abilities/effects/AuraReplacementEffect.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java index d63917d5f58..be9335c3c63 100644 --- a/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/AuraReplacementEffect.java @@ -121,7 +121,7 @@ public class AuraReplacementEffect extends ReplacementEffectImpl { } } - game.applyEffects(); // So continuousEffects are removed if previous effect of the same ability did move objects that cuase continuous effects + game.applyEffects(); // So continuousEffects are removed if previous effect of the same ability did move objects that cause continuous effects Player controllingPlayer = null; if (targetId == null) { SpellAbility spellAbility = card.getSpellAbility(); From c33a731a4abe157735f6462048fd572095a3a374 Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Wed, 7 Sep 2016 23:31:26 -0400 Subject: [PATCH 5/7] Refactor the core zone change code to use a common code path. --- .../cards/triggers/WorldgorgerDragonTest.java | 1 + .../abilities/effects/common/MeldEffect.java | 3 +- Mage/src/main/java/mage/cards/CardImpl.java | 144 ++------- Mage/src/main/java/mage/cards/MeldCard.java | 273 +++++------------- .../main/java/mage/game/ZoneChangeInfo.java | 161 +++++++++++ .../src/main/java/mage/game/ZonesHandler.java | 260 +++++++++++++++++ .../mage/game/permanent/PermanentCard.java | 74 +---- .../mage/game/permanent/PermanentMeld.java | 104 ------- .../main/java/mage/players/PlayerImpl.java | 89 +----- 9 files changed, 539 insertions(+), 570 deletions(-) create mode 100644 Mage/src/main/java/mage/game/ZoneChangeInfo.java create mode 100644 Mage/src/main/java/mage/game/ZonesHandler.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java index a34a607094f..32d6d418de6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/WorldgorgerDragonTest.java @@ -86,6 +86,7 @@ public class WorldgorgerDragonTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Staunch Defenders", 1); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Animate Dead", "Worldgorger Dragon"); + setChoice(playerA, "When {this} enters the battlefield, if it's"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); diff --git a/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java index 7f2f4c6af7e..1c28325374e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java @@ -101,10 +101,11 @@ public class MeldEffect extends OneShotEffect { if (!sourceCard.isCopy() && !meldWithCard.isCopy()) { meldCard.setOwnerId(controller.getId()); meldCard.setTopHalfCard(meldWithCard, game); - meldCard.setbottomHalfCard(sourceCard, game); + meldCard.setBottomHalfCard(sourceCard, game); meldCard.setMelded(true); game.addMeldCard(meldCard.getId(), meldCard); game.getState().addCard(meldCard); + meldCard.setZone(Zone.EXILED, game); meldCard.moveToZone(Zone.BATTLEFIELD, sourceId, game, false); } return true; diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 9b12c761f29..8b7b353512d 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -28,10 +28,8 @@ package mage.cards; import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; +import java.util.*; + import mage.MageObject; import mage.MageObjectImpl; import mage.Mana; @@ -50,10 +48,7 @@ import mage.constants.TimingRule; import mage.constants.Zone; import mage.counters.Counter; import mage.counters.Counters; -import mage.game.CardAttribute; -import mage.game.CardState; -import mage.game.Game; -import mage.game.GameState; +import mage.game.*; import mage.game.command.Commander; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; @@ -61,6 +56,7 @@ import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; import mage.game.stack.Spell; import mage.game.stack.StackObject; +import mage.players.Player; import mage.util.GameLog; import mage.watchers.Watcher; import org.apache.log4j.Logger; @@ -344,79 +340,24 @@ public abstract class CardImpl extends MageObjectImpl implements Card { public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { Zone fromZone = game.getState().getZone(objectId); ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, toZone, appliedEffects); - if (!game.replaceEvent(event)) { - removeFromZone(game, fromZone, sourceId); - setFaceDown(false, game); - updateZoneChangeCounter(game); - switch (event.getToZone()) { - case GRAVEYARD: - game.getPlayer(ownerId).putInGraveyard(this, game); - break; - case HAND: - game.getPlayer(ownerId).getHand().add(this); - break; - case STACK: - game.getStack().push(new Spell(this, this.getSpellAbility().copy(), ownerId, event.getFromZone())); - break; - case EXILED: - game.getExile().getPermanentExile().add(this); - break; - case COMMAND: - game.addCommander(new Commander(this)); - break; - case LIBRARY: - if (flag) { - game.getPlayer(ownerId).getLibrary().putOnTop(this, game); - } else { - game.getPlayer(ownerId).getLibrary().putOnBottom(this, game); - } - break; - case BATTLEFIELD: - PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game); // controller can be replaced (e.g. Gather Specimens) - game.addPermanent(permanent); - game.setZone(objectId, Zone.BATTLEFIELD); - game.setScopeRelevant(true); - game.applyEffects(); - boolean entered = permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); - game.setScopeRelevant(false); - game.applyEffects(); - if (entered) { - if (flag) { - permanent.setTapped(true); - } - event.setTarget(permanent); - } else { - return false; - } - break; - default: - Card sourceCard = game.getCard(sourceId); - logger.fatal(new StringBuilder("Invalid to zone [").append(toZone) - .append("] for card [").append(this.getName()) - .append("] to zone [").append(toZone) - .append("] source [").append(sourceCard != null ? sourceCard.getName() : "null").append("]").toString()); - return false; - } - game.setZone(objectId, event.getToZone()); - game.addSimultaneousEvent(event); - return game.getState().getZone(objectId) == toZone; + ZoneChangeInfo zoneChangeInfo; + if (toZone == Zone.LIBRARY) { + zoneChangeInfo = new ZoneChangeInfo.Library(event, flag /* put on top */); + } else if (toZone == Zone.BATTLEFIELD) { + zoneChangeInfo = new ZoneChangeInfo.Battlefield(event, flag /* comes into play tapped */); + } else { + zoneChangeInfo = new ZoneChangeInfo(event); } - return false; + return ZonesHandler.moveCard(zoneChangeInfo, game); } @Override public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) { Card mainCard = getMainCard(); ZoneChangeEvent event = new ZoneChangeEvent(mainCard.getId(), ability.getId(), controllerId, fromZone, Zone.STACK); - if (!game.replaceEvent(event)) { - mainCard.removeFromZone(game, fromZone, ability.getSourceId()); - game.getStack().push(new Spell(this, ability.copy(), controllerId, event.getFromZone())); - updateZoneChangeCounter(game); - setZone(event.getToZone(), game); - game.fireEvent(event); - return game.getState().getZone(mainCard.getId()) == Zone.STACK; - } - return false; + ZoneChangeInfo.Stack info = + new ZoneChangeInfo.Stack(event, new Spell(this, ability.copy(), controllerId, event.getFromZone())); + return ZonesHandler.cast(info, game); } @Override @@ -428,20 +369,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card { public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { Zone fromZone = game.getState().getZone(objectId); ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); - if (!game.replaceEvent(event)) { - removeFromZone(game, fromZone, sourceId); - if (exileId == null) { - game.getExile().getPermanentExile().add(this); - } else { - game.getExile().createZone(exileId, name).add(this); - } - setFaceDown(false, game); - updateZoneChangeCounter(game); - game.setZone(objectId, event.getToZone()); - game.addSimultaneousEvent(event); - return true; - } - return false; + ZoneChangeInfo.Exile info = new ZoneChangeInfo.Exile(event, exileId, name); + return ZonesHandler.moveCard(info, game); } @Override @@ -455,44 +384,15 @@ public abstract class CardImpl extends MageObjectImpl implements Card { } @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown) { - return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, tapped, facedown, null); + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean faceDown) { + return this.putOntoBattlefield(game, fromZone, sourceId, controllerId, tapped, faceDown, null); } @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown, ArrayList appliedEffects) { + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean faceDown, ArrayList appliedEffects) { ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, controllerId, fromZone, Zone.BATTLEFIELD, appliedEffects); - if (facedown) { - this.setFaceDown(true, game); - } - if (!game.replaceEvent(event)) { - if (facedown) { - this.setFaceDown(false, game); - } - removeFromZone(game, fromZone, sourceId); - updateZoneChangeCounter(game); - PermanentCard permanent = new PermanentCard(this, event.getPlayerId(), game); - // make sure the controller of all continuous effects of this card are switched to the current controller - game.getContinuousEffects().setController(objectId, event.getPlayerId()); - game.addPermanent(permanent); - setZone(Zone.BATTLEFIELD, game); - // check if there are counters to add to the permanent (e.g. from non replacement effects like Persist) - checkForCountersToAdd(permanent, game); - game.setScopeRelevant(true); - permanent.setTapped(tapped); - permanent.setFaceDown(facedown, game); - boolean entered = permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); - game.setScopeRelevant(false); - game.applyEffects(); - if (entered) { - game.addSimultaneousEvent(new ZoneChangeEvent(permanent, event.getPlayerId(), fromZone, Zone.BATTLEFIELD)); - return true; - } - } - if (facedown) { - this.setFaceDown(false, game); - } - return false; + ZoneChangeInfo.Battlefield info = new ZoneChangeInfo.Battlefield(event, faceDown, tapped); + return ZonesHandler.moveCard(info, game); } @Override diff --git a/Mage/src/main/java/mage/cards/MeldCard.java b/Mage/src/main/java/mage/cards/MeldCard.java index 952b60122bc..9644ddc680f 100644 --- a/Mage/src/main/java/mage/cards/MeldCard.java +++ b/Mage/src/main/java/mage/cards/MeldCard.java @@ -35,6 +35,7 @@ import mage.constants.Zone; import mage.counters.Counter; import mage.game.Game; import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; import mage.game.permanent.PermanentMeld; import mage.players.Player; @@ -49,9 +50,11 @@ public abstract class MeldCard extends CardImpl { protected int topLastZoneChangeCounter; protected int bottomLastZoneChangeCounter; protected boolean isMelded; + protected Cards halves; public MeldCard(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs) { super(ownerId, cardNumber, name, rarity, cardTypes, costs); + halves = new CardsImpl(); } public MeldCard(MeldCard card) { @@ -60,6 +63,7 @@ public abstract class MeldCard extends CardImpl { this.bottomHalfCard = card.bottomHalfCard; this.topLastZoneChangeCounter = card.topLastZoneChangeCounter; this.bottomLastZoneChangeCounter = card.bottomLastZoneChangeCounter; + this.halves = new CardsImpl(halves); this.isMelded = card.isMelded; } @@ -78,6 +82,7 @@ public abstract class MeldCard extends CardImpl { public void setTopHalfCard(Card topHalfCard, Game game) { this.topHalfCard = topHalfCard; this.topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); + halves.add(topHalfCard.getId()); } public int getTopLastZoneChangeCounter() { @@ -92,9 +97,10 @@ public abstract class MeldCard extends CardImpl { return bottomHalfCard; } - public void setbottomHalfCard(Card bottomHalfCard, Game game) { + public void setBottomHalfCard(Card bottomHalfCard, Game game) { this.bottomHalfCard = bottomHalfCard; this.bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); + halves.add(bottomHalfCard.getId()); } public int getBottomLastZoneChangeCounter() { @@ -112,204 +118,6 @@ public abstract class MeldCard extends CardImpl { bottomHalfCard.assignNewId(); } - @Override - public void setCopy(boolean isCopy) { - super.setCopy(isCopy); - topHalfCard.setCopy(isCopy); - bottomHalfCard.setCopy(isCopy); - } - - @Override - public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { - if (this.isMelded()) { - // Initial move to battlefield - if (toZone == Zone.BATTLEFIELD) { - return this.putOntoBattlefield(game, Zone.EXILED, sourceId, this.getOwnerId(), false, false, appliedEffects); - } // Move when melded from the battlefield to elsewhere - else { - ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.BATTLEFIELD, toZone, appliedEffects); - if (!game.replaceEvent(event)) { - updateZoneChangeCounter(game); - switch (event.getToZone()) { - case GRAVEYARD: - game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game); - game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game); - break; - case HAND: - game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); - game.getPlayer(this.getOwnerId()).getHand().add(bottomHalfCard); - break; - case EXILED: - game.getExile().getPermanentExile().add(topHalfCard); - game.getExile().getPermanentExile().add(bottomHalfCard); - break; - case LIBRARY: - Player controller = game.getPlayer(this.getOwnerId()); - if (controller != null) { - CardsImpl cardsToMove = new CardsImpl(); - cardsToMove.add(topHalfCard); - cardsToMove.add(bottomHalfCard); - if (flag) { - controller.putCardsOnTopOfLibrary(cardsToMove, game, null, true); - } else { - controller.putCardsOnBottomOfLibrary(cardsToMove, game, null, true); - } - } - break; - default: - return false; - } - this.setMelded(false); - game.setZone(topHalfCard.getId(), event.getToZone()); - game.setZone(bottomHalfCard.getId(), event.getToZone()); - this.topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); - this.bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); - game.addSimultaneousEvent(event); - return true; - } else { - return false; - } - } - } else { - // Try to move the former meld cards after it has already left the battlefield. - // If the meld parts didn't move from that zone, move them instead of the meld card. - // Reset the local zcc so the meld card lose track of them. - boolean returnValue = false; - if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { - topHalfCard.moveToZone(toZone, sourceId, game, flag, appliedEffects); - topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); - returnValue = true; - } - if (bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game)) { - bottomHalfCard.moveToZone(toZone, sourceId, game, flag, appliedEffects); - bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); - returnValue = true; - } - return returnValue; - } - } - - @Override - public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { - if (this.isMelded()) { - // Move when melded from the battlefield to exile - ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.BATTLEFIELD, Zone.EXILED, appliedEffects); - if (!game.replaceEvent(event)) { - updateZoneChangeCounter(game); - switch (event.getToZone()) { - case GRAVEYARD: - game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game); - game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game); - break; - case HAND: - game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); - game.getPlayer(this.getOwnerId()).getHand().add(bottomHalfCard); - break; - case EXILED: - if (exileId == null) { - game.getExile().getPermanentExile().add(topHalfCard); - game.getExile().getPermanentExile().add(bottomHalfCard); - } else { - game.getExile().createZone(exileId, name).add(topHalfCard); - game.getExile().getExileZone(exileId).add(bottomHalfCard); - } - break; - case LIBRARY: - Player controller = game.getPlayer(this.getOwnerId()); - if (controller != null) { - CardsImpl cardsToMove = new CardsImpl(); - cardsToMove.add(topHalfCard); - cardsToMove.add(bottomHalfCard); - if (event.getFlag()) { - controller.putCardsOnTopOfLibrary(cardsToMove, game, null, true); - } else { - controller.putCardsOnBottomOfLibrary(cardsToMove, game, null, true); - } - } - break; - default: - return false; - } - this.setMelded(false); - game.setZone(topHalfCard.getId(), event.getToZone()); - game.setZone(bottomHalfCard.getId(), event.getToZone()); - this.topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); - this.bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); - game.addSimultaneousEvent(event); - return true; - } else { - return false; - } - } else { - // Try to move the former meld cards after it has already left the battlefield. - // If the meld parts didn't move from that zone, move them instead of the meld card. - // Reset the local zcc so the meld card lose track of them. - boolean returnValue = false; - if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { - topHalfCard.moveToExile(exileId, name, sourceId, game, appliedEffects); - topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); - returnValue = true; - } - if (bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game)) { - bottomHalfCard.moveToExile(exileId, name, sourceId, game, appliedEffects); - bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); - returnValue = true; - } - return returnValue; - } - } - - @Override - public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown, ArrayList appliedEffects) { - // Initial move to battlefield - if (this.isMelded()) { - ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, controllerId, Zone.EXILED, Zone.BATTLEFIELD, appliedEffects); - if (!game.replaceEvent(event) && event.getToZone() == Zone.BATTLEFIELD) { - updateZoneChangeCounter(game); - PermanentMeld permanent = new PermanentMeld(this, event.getPlayerId(), game); // controller can be replaced (e.g. Gather Specimens) - game.addPermanent(permanent); - game.setZone(objectId, Zone.BATTLEFIELD); - game.setScopeRelevant(true); - game.applyEffects(); - boolean entered = permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); - game.setScopeRelevant(false); - game.applyEffects(); - if (entered) { - if (tapped) { - permanent.setTapped(true); - } - event.setTarget(permanent); - } else { - return false; - } - game.setZone(objectId, event.getToZone()); - game.addSimultaneousEvent(event); - game.getExile().removeCard(this.topHalfCard, game); - game.getExile().removeCard(this.bottomHalfCard, game); - return true; - } else { - this.setMelded(false); - return false; - } - } else { - // Try to move the former meld cards after it has already left the battlefield. - // If the meld parts didn't move from that zone, move them instead of the meld card. - // Reset the local zcc so the meld card lose track of them. - boolean returnValue = false; - if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { - topHalfCard.moveToZone(Zone.BATTLEFIELD, sourceId, game, tapped, appliedEffects); - topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); - returnValue = true; - } - if (bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game)) { - bottomHalfCard.moveToZone(Zone.BATTLEFIELD, sourceId, game, tapped, appliedEffects); - bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); - returnValue = true; - } - return returnValue; - } - } - @Override public void setOwnerId(UUID ownerId) { super.setOwnerId(ownerId); @@ -333,13 +141,76 @@ public abstract class MeldCard extends CardImpl { } else { // can this really happen? boolean returnState = true; - if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { + if (hasTopHalf(game)) { returnState |= topHalfCard.addCounters(counter, game, appliedEffects); } - if (bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game)) { + if (hasBottomHalf(game)) { returnState |= bottomHalfCard.addCounters(counter, game, appliedEffects); } return returnState; } } + + public boolean hasTopHalf(Game game) { + boolean value = topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game) + && halves.contains(topHalfCard.getId()); + if (!value) { + halves.remove(topHalfCard); + } + return value; + } + + public boolean hasBottomHalf(Game game) { + boolean value = bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game) + && halves.contains(bottomHalfCard.getId()); + if (!value) { + halves.remove(bottomHalfCard); + } + return value; + } + + @Override + public boolean removeFromZone(Game game, Zone fromZone, UUID sourceId) { + if (isCopy()) { + return super.removeFromZone(game, fromZone, sourceId); + } + if (isMelded() && fromZone == Zone.BATTLEFIELD) { + Permanent permanent = game.getPermanent(objectId); + return permanent != null && permanent.removeFromZone(game, fromZone, sourceId); + } + boolean topRemoved = hasTopHalf(game) && topHalfCard.removeFromZone(game, fromZone, sourceId); + if (!topRemoved) { + // The top half isn't being moved with the pair anymore. + halves.remove(topHalfCard); + } + boolean bottomRemoved = hasBottomHalf(game) && bottomHalfCard.removeFromZone(game, fromZone, sourceId); + if (!bottomRemoved) { + // The bottom half isn't being moved with the pair anymore. + halves.remove(bottomHalfCard); + } + return topRemoved || bottomRemoved; + } + + @Override + public void updateZoneChangeCounter(Game game) { + if (isCopy() || !isMelded()) { + super.updateZoneChangeCounter(game); + return; + } + game.getState().updateZoneChangeCounter(objectId); + if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game) + && halves.contains(topHalfCard.getId())) { + topHalfCard.updateZoneChangeCounter(game); + topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); + } + if (bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game) + && halves.contains(bottomHalfCard.getId())) { + bottomHalfCard.updateZoneChangeCounter(game); + bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); + } + } + + public Cards getHalves() { + return halves; + } } diff --git a/Mage/src/main/java/mage/game/ZoneChangeInfo.java b/Mage/src/main/java/mage/game/ZoneChangeInfo.java new file mode 100644 index 00000000000..7c8936d0b1d --- /dev/null +++ b/Mage/src/main/java/mage/game/ZoneChangeInfo.java @@ -0,0 +1,161 @@ +package mage.game; + +import mage.cards.MeldCard; +import mage.constants.Zone; +import mage.game.events.ZoneChangeEvent; +import mage.game.stack.Spell; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Created by Dilnu on 9/4/16. + */ +public class ZoneChangeInfo { + public boolean faceDown; + public ZoneChangeEvent event; + + public ZoneChangeInfo(ZoneChangeEvent event) { + this.event = event; + this.faceDown = false; + } + + public ZoneChangeInfo(ZoneChangeEvent event, boolean faceDown) { + this(event); + this.faceDown = faceDown; + } + + public ZoneChangeInfo(ZoneChangeInfo info) { + this.event = info.event; + this.faceDown = info.faceDown; + } + + public ZoneChangeInfo copy() { + return new ZoneChangeInfo(this); + } + + public static class Library extends ZoneChangeInfo { + public boolean top; + + public Library(ZoneChangeEvent event, boolean top) { + super(event); + this.top = top; + } + + public Library(ZoneChangeEvent event, boolean faceDown, boolean top) { + super(event, faceDown); + this.top = top; + } + + public Library(Library info) { + super(info); + this.top = info.top; + } + + @Override + public ZoneChangeInfo copy() { + return new Library(this); + } + } + + public static class Exile extends ZoneChangeInfo { + public UUID id; + public String name; + + public Exile(ZoneChangeEvent event, UUID id, String name) { + super(event); + this.id = id; + this.name = name; + } + + public Exile(ZoneChangeEvent event, boolean faceDown, UUID id, String name) { + super(event, faceDown); + this.id = id; + this.name = name; + } + + public Exile(Exile info) { + super(info); + this.id = info.id; + this.name = info.name; + } + + @Override + public ZoneChangeInfo copy() { + return new Exile(this); + } + } + + public static class Battlefield extends ZoneChangeInfo { + public boolean tapped; + + public Battlefield (ZoneChangeEvent event, boolean tapped) { + super(event); + this.tapped = tapped; + } + + public Battlefield (ZoneChangeEvent event, boolean faceDown, boolean tapped) { + super(event, faceDown); + this.tapped = tapped; + } + + public Battlefield(Battlefield info) { + super(info); + this.tapped = info.tapped; + } + + @Override + public ZoneChangeInfo copy() { + return new Battlefield(this); + } + } + + public static class Stack extends ZoneChangeInfo { + public Spell spell; + + public Stack(ZoneChangeEvent event, Spell spell) { + super(event); + this.spell = spell; + } + + public Stack(ZoneChangeEvent event, boolean faceDown, Spell spell) { + super(event, faceDown); + this.spell = spell; + } + + public Stack(Stack info) { + super(info); + this.spell = info.spell; + } + + @Override + public ZoneChangeInfo copy() { + return new Stack(this); + } + } + + public static class Unmelded extends ZoneChangeInfo { + List subInfo = new ArrayList<>(); + public Unmelded(ZoneChangeInfo info, Game game) { + super(info.event); + MeldCard meld = game.getMeldCard(info.event.getTargetId()); + if (meld != null) { + if (meld.hasTopHalf(game)) { + ZoneChangeEvent topEvent = new ZoneChangeEvent(meld.getTopHalfCard().getId(), event.getSourceId(), + event.getPlayerId(), event.getFromZone(), event.getToZone(), event.getAppliedEffects()); + ZoneChangeInfo topInfo = info.copy(); + topInfo.event = topEvent; + subInfo.add(topInfo); + } + if (meld.hasBottomHalf(game)) { + ZoneChangeEvent bottomEvent = new ZoneChangeEvent(meld.getBottomHalfCard().getId(), event.getSourceId(), + event.getPlayerId(), event.getFromZone(), event.getToZone(), event.getAppliedEffects()); + ZoneChangeInfo bottomInfo = info.copy(); + bottomInfo.event = bottomEvent; + subInfo.add(bottomInfo); + } + } + } + } +} diff --git a/Mage/src/main/java/mage/game/ZonesHandler.java b/Mage/src/main/java/mage/game/ZonesHandler.java new file mode 100644 index 00000000000..71e35447382 --- /dev/null +++ b/Mage/src/main/java/mage/game/ZonesHandler.java @@ -0,0 +1,260 @@ +package mage.game; + +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.cards.MeldCard; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.command.Commander; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; +import mage.game.permanent.PermanentMeld; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetCard; + +import java.util.*; + +/** + * Created by samuelsandeen on 9/6/16. + */ +public class ZonesHandler { + public static boolean cast(ZoneChangeInfo info, Game game) { + if (maybeRemoveFromSourceZone(info, game)) { + placeInDestinationZone(info, game); + game.fireEvent(info.event); + return true; + } + return false; + } + + public static boolean moveCard(ZoneChangeInfo info, Game game) { + List list = new ArrayList(); + list.add(info); + return moveCards(list, game).size() > 0; + } + + public static List moveCards(List zoneChangeInfos, Game game) { + // Handle Unmelded Meld Cards + for(ListIterator itr = zoneChangeInfos.listIterator(); itr.hasNext();) { + ZoneChangeInfo info = itr.next(); + MeldCard card = game.getMeldCard(info.event.getTargetId()); + // Copies should be handled as normal cards. + if (card != null && !card.isMelded() && !card.isCopy()) { + ZoneChangeInfo.Unmelded unmelded = new ZoneChangeInfo.Unmelded(info, game); + if (unmelded.subInfo.isEmpty()) { + itr.remove(); + } else { + itr.set(unmelded); + } + } + } + for (Iterator itr = zoneChangeInfos.iterator(); itr.hasNext();) { + if (!maybeRemoveFromSourceZone(itr.next(), game)) { + itr.remove(); + } + } + for (ZoneChangeInfo zoneChangeInfo : zoneChangeInfos) { + placeInDestinationZone(zoneChangeInfo, game); + game.addSimultaneousEvent(zoneChangeInfo.event); + } + return zoneChangeInfos; + } + + private static void placeInDestinationZone(ZoneChangeInfo info, Game game) { + // Handle unmelded cards + if (info instanceof ZoneChangeInfo.Unmelded) { + ZoneChangeInfo.Unmelded unmelded = (ZoneChangeInfo.Unmelded) info; + Zone toZone = null; + for (ZoneChangeInfo subInfo : unmelded.subInfo) { + toZone = subInfo.event.getToZone(); + placeInDestinationZone(subInfo, game); + } + if (toZone != null) { + game.setZone(unmelded.event.getTargetId(), toZone); + } + return; + } + // Handle normal cases + ZoneChangeEvent event = info.event; + Zone toZone = event.getToZone(); + Card targetCard = game.getCard(event.getTargetId()); + Cards cards; + if (targetCard instanceof MeldCard) { + cards = ((MeldCard) targetCard).getHalves(); + } else { + cards = new CardsImpl(targetCard); + } + Player owner = game.getPlayer(targetCard.getOwnerId()); + switch (toZone) { + case HAND: + for (Card card : cards.getCards(game)) { + game.getPlayer(card.getOwnerId()).getHand().add(card); + } + break; + case GRAVEYARD: + for (Card card : chooseOrder( + "order to put in graveyard (last chosen will be on top)", cards, owner, game)) { + game.getPlayer(card.getOwnerId()).getGraveyard().add(card); + } + break; + case LIBRARY: + if (info instanceof ZoneChangeInfo.Library && ((ZoneChangeInfo.Library) info).top) { + for (Card card : chooseOrder( + "order to put on top of library (last chosen will be topmost)", cards, owner, game)) { + game.getPlayer(card.getOwnerId()).getLibrary().putOnTop(card, game); + } + } else { + for (Card card : chooseOrder( + "order to put on bottom of library (last chosen will be bottommost)", cards, owner, game)) { + game.getPlayer(card.getOwnerId()).getLibrary().putOnBottom(card, game); + } + } + break; + case EXILED: + for (Card card : cards.getCards(game)) { + if (info instanceof ZoneChangeInfo.Exile && ((ZoneChangeInfo.Exile) info).id != null) { + ZoneChangeInfo.Exile exileInfo = (ZoneChangeInfo.Exile) info; + game.getExile().createZone(exileInfo.id, exileInfo.name).add(card); + } else { + game.getExile().getPermanentExile().add(card); + } + } + break; + case COMMAND: + // There should never be more than one card here. + for (Card card : cards.getCards(game)) { + game.addCommander(new Commander(card)); + } + break; + case STACK: + // There should never be more than one card here. + for (Card card : cards.getCards(game)) { + if (info instanceof ZoneChangeInfo.Stack && ((ZoneChangeInfo.Stack) info).spell != null) { + game.getStack().push(((ZoneChangeInfo.Stack) info).spell); + } else { + game.getStack().push( + new Spell(card, card.getSpellAbility().copy(), card.getOwnerId(), event.getFromZone())); + } + } + break; + case BATTLEFIELD: + Permanent permanent = event.getTarget(); + game.addPermanent(permanent); + game.getPermanentsEntering().remove(permanent.getId()); + break; + default: + throw new UnsupportedOperationException("to Zone" + toZone.toString() + " not supported yet"); + } + game.setZone(event.getTargetId(), event.getToZone()); + if (targetCard instanceof MeldCard) { + if (event.getToZone() != Zone.BATTLEFIELD) { + ((MeldCard) targetCard).setMelded(false); + } + for (Card card : cards.getCards(game)) { + game.setZone(card.getId(), event.getToZone()); + } + } + } + + private static boolean maybeRemoveFromSourceZone(ZoneChangeInfo info, Game game) { + // Handle Unmelded Cards + if (info instanceof ZoneChangeInfo.Unmelded) { + ZoneChangeInfo.Unmelded unmelded = (ZoneChangeInfo.Unmelded) info; + MeldCard meld = game.getMeldCard(info.event.getTargetId()); + for (Iterator itr = unmelded.subInfo.iterator(); itr.hasNext();) { + ZoneChangeInfo subInfo = itr.next(); + if (!maybeRemoveFromSourceZone(subInfo, game)) { + itr.remove(); + } else { + if (subInfo.event.getTargetId() == meld.getTopHalfCard().getId()) { + meld.setTopLastZoneChangeCounter(meld.getTopHalfCard().getZoneChangeCounter(game)); + } else if (subInfo.event.getTargetId() == meld.getBottomHalfCard().getId()) { + meld.setBottomLastZoneChangeCounter(meld.getBottomHalfCard().getZoneChangeCounter(game)); + } + } + } + if (unmelded.subInfo.isEmpty()) { + return false; + } + meld.updateZoneChangeCounter(game); + return true; + } + // Handle all normal cases + ZoneChangeEvent event = info.event; + Card card = game.getCard(event.getTargetId()); + boolean success = false; + if (info.faceDown) { + card.setFaceDown(true, game); + } + if(!game.replaceEvent(event)) { + Zone fromZone = event.getFromZone(); + if (event.getToZone() == Zone.BATTLEFIELD) { + // controlling player can be replaced so use event player now + Permanent permanent; + if (card instanceof MeldCard) { + permanent = new PermanentMeld(card, event.getPlayerId(), game); + } else { + permanent = new PermanentCard(card, event.getPlayerId(), game); + } + game.getPermanentsEntering().put(permanent.getId(), permanent); + card.checkForCountersToAdd(permanent, game); + permanent.setTapped( + info instanceof ZoneChangeInfo.Battlefield && ((ZoneChangeInfo.Battlefield) info).tapped); + permanent.setFaceDown(info.faceDown, game); + + if (info.faceDown) { + card.setFaceDown(false, game); + } + + // make sure the controller of all continuous effects of this card are switched to the current controller + game.setScopeRelevant(true); + game.getContinuousEffects().setController(permanent.getId(), permanent.getControllerId()); + if (permanent.entersBattlefield(event.getSourceId(), game, fromZone, true) + && card.removeFromZone(game, fromZone, event.getSourceId())) { + success = true; + event.setTarget(permanent); + } else { + // revert controller to owner if permanent does not enter + game.getContinuousEffects().setController(permanent.getId(), permanent.getOwnerId()); + game.getPermanentsEntering().remove(permanent.getId()); + } + game.setScopeRelevant(false); + } else if (event.getTarget() != null) { + card.setFaceDown(info.faceDown, game); + Permanent target = event.getTarget(); + success = game.getPlayer(target.getControllerId()).removeFromBattlefield(target, game) + && target.removeFromZone(game, fromZone, event.getSourceId()); + } else { + card.setFaceDown(info.faceDown, game); + success = card.removeFromZone(game, fromZone, event.getSourceId()); + } + } + if (success) { + if (event.getToZone() == Zone.BATTLEFIELD && event.getTarget() != null) { + event.getTarget().updateZoneChangeCounter(game); + } else { + card.updateZoneChangeCounter(game); + } + } + return success; + } + + public static List chooseOrder(String message, Cards cards, Player player, Game game) { + List order = new ArrayList(); + TargetCard target = new TargetCard(Zone.ALL, new FilterCard(message)); + target.setRequired(true); + while (player.isInGame() && cards.size() > 1) { + player.choose(Outcome.Neutral, cards, target, game); + UUID targetObjectId = target.getFirstTarget(); + order.add(cards.get(targetObjectId, game)); + cards.remove(targetObjectId); + target.clearChosen(); + } + order.add(cards.getCards(game).iterator().next()); + return order; + } +} diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java index 269cb3dab06..ae7b298d55f 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java @@ -38,9 +38,8 @@ import mage.cards.Card; import mage.cards.LevelerCard; import mage.constants.Zone; import mage.game.Game; -import mage.game.ZoneInfo; +import mage.game.ZoneChangeInfo; import mage.game.ZonesHandler; -import mage.game.command.Commander; import mage.game.events.ZoneChangeEvent; import mage.players.Player; @@ -147,82 +146,29 @@ public class PermanentCard extends PermanentImpl { return card; } - @Override - public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag) { - return moveToZone(toZone, sourceId, game, flag, null); - } - @Override public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { Zone fromZone = game.getState().getZone(objectId); Player controller = game.getPlayer(controllerId); if (controller != null) { ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, controllerId, fromZone, toZone, appliedEffects); - if (!game.replaceEvent(event)) { - controller.removeFromBattlefield(this, game); - Player owner = game.getPlayer(ownerId); - game.rememberLKI(objectId, Zone.BATTLEFIELD, this); - if (owner != null) { - card.updateZoneChangeCounter(game); - switch (event.getToZone()) { - case GRAVEYARD: - owner.putInGraveyard(card, game); - break; - case HAND: - owner.getHand().add(card); - break; - case EXILED: - game.getExile().getPermanentExile().add(card); - break; - case COMMAND: - game.addCommander(new Commander(card)); - break; - case LIBRARY: - if (flag) { - owner.getLibrary().putOnTop(card, game); - } else { - owner.getLibrary().putOnBottom(card, game); - } - break; - case BATTLEFIELD: - //should never happen - break; - } - game.setZone(objectId, event.getToZone()); - game.addSimultaneousEvent(event); - return game.getState().getZone(objectId) == toZone; - } + ZoneChangeInfo zoneChangeInfo; + if (toZone == Zone.LIBRARY) { + zoneChangeInfo = new ZoneChangeInfo.Library(event, flag /* put on top */); + } else { + zoneChangeInfo = new ZoneChangeInfo(event); } + return ZonesHandler.moveCard(zoneChangeInfo, game); } return false; } - @Override - public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game) { - return moveToExile(exileId, name, sourceId, game, null); - } - @Override public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { Zone fromZone = game.getState().getZone(objectId); - Player controller = game.getPlayer(controllerId); - if (controller != null && controller.removeFromBattlefield(this, game)) { - ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); - if (!game.replaceEvent(event)) { - game.rememberLKI(objectId, Zone.BATTLEFIELD, this); - // update zone change counter of original card - card.updateZoneChangeCounter(game); - if (exileId == null) { - game.getExile().getPermanentExile().add(card); - } else { - game.getExile().createZone(exileId, name).add(card); - } - game.setZone(objectId, event.getToZone()); - game.addSimultaneousEvent(event); - return true; - } - } - return false; + ZoneChangeEvent event = new ZoneChangeEvent(this, sourceId, ownerId, fromZone, Zone.EXILED, appliedEffects); + ZoneChangeInfo.Exile info = new ZoneChangeInfo.Exile(event, exileId, name); + return ZonesHandler.moveCard(info, game); } @Override diff --git a/Mage/src/main/java/mage/game/permanent/PermanentMeld.java b/Mage/src/main/java/mage/game/permanent/PermanentMeld.java index ed649a0d1b8..dd28172322b 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentMeld.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentMeld.java @@ -55,108 +55,4 @@ public class PermanentMeld extends PermanentCard { return this.getCard().getConvertedManaCost(); } } - - @Override - public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { - ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.BATTLEFIELD, toZone, appliedEffects); - if (!game.replaceEvent(event)) { - Player controller = game.getPlayer(this.getControllerId()); - if (controller != null) { - controller.removeFromBattlefield(this, game); - updateZoneChangeCounter(game); - MeldCard meldCard = (MeldCard) this.getCard(); - Card topHalfCard = meldCard.getTopHalfCard(); - Card bottomHalfCard = meldCard.getBottomHalfCard(); - switch (event.getToZone()) { - case GRAVEYARD: - game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game); - game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game); - break; - case HAND: - game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); - game.getPlayer(this.getOwnerId()).getHand().add(bottomHalfCard); - break; - case EXILED: - game.getExile().getPermanentExile().add(topHalfCard); - game.getExile().getPermanentExile().add(bottomHalfCard); - break; - case LIBRARY: - CardsImpl cardsToMove = new CardsImpl(); - cardsToMove.add(topHalfCard); - cardsToMove.add(bottomHalfCard); - if (flag) { - controller.putCardsOnTopOfLibrary(cardsToMove, game, null, true); - } else { - controller.putCardsOnBottomOfLibrary(cardsToMove, game, null, true); - } - break; - default: - return false; - } - meldCard.setMelded(false); - game.setZone(topHalfCard.getId(), event.getToZone()); - game.setZone(bottomHalfCard.getId(), event.getToZone()); - meldCard.setTopLastZoneChangeCounter(topHalfCard.getZoneChangeCounter(game)); - meldCard.setBottomLastZoneChangeCounter(bottomHalfCard.getZoneChangeCounter(game)); - game.addSimultaneousEvent(event); - return true; - } - } - return false; - } - - @Override - public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { - ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.BATTLEFIELD, Zone.EXILED, appliedEffects); - if (!game.replaceEvent(event)) { - Player controller = game.getPlayer(this.getControllerId()); - if (controller != null) { - controller.removeFromBattlefield(this, game); - updateZoneChangeCounter(game); - MeldCard meldCard = (MeldCard) this.getCard(); - Card topHalfCard = meldCard.getTopHalfCard(); - Card bottomHalfCard = meldCard.getBottomHalfCard(); - switch (event.getToZone()) { - case GRAVEYARD: - game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game); - game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game); - break; - case HAND: - game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); - game.getPlayer(this.getOwnerId()).getHand().add(bottomHalfCard); - break; - case EXILED: - if (exileId == null) { - game.getExile().getPermanentExile().add(topHalfCard); - game.getExile().getPermanentExile().add(bottomHalfCard); - } else { - game.getExile().createZone(exileId, name).add(topHalfCard); - game.getExile().getExileZone(exileId).add(bottomHalfCard); - } - break; - case LIBRARY: - CardsImpl cardsToMove = new CardsImpl(); - cardsToMove.add(topHalfCard); - cardsToMove.add(bottomHalfCard); - if (event.getFlag()) { - controller.putCardsOnTopOfLibrary(cardsToMove, game, null, true); - } else { - controller.putCardsOnBottomOfLibrary(cardsToMove, game, null, true); - } - break; - default: - return false; - } - meldCard.setMelded(false); - game.setZone(meldCard.getId(), Zone.OUTSIDE); - game.setZone(topHalfCard.getId(), event.getToZone()); - game.setZone(bottomHalfCard.getId(), event.getToZone()); - meldCard.setTopLastZoneChangeCounter(topHalfCard.getZoneChangeCounter(game)); - meldCard.setBottomLastZoneChangeCounter(bottomHalfCard.getZoneChangeCounter(game)); - game.addSimultaneousEvent(event); - return true; - } - } - return false; - } } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 8f716fcd9db..a115954b886 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -107,10 +107,7 @@ import mage.filter.common.FilterCreatureForCombat; import mage.filter.common.FilterCreatureForCombatBlock; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.PermanentIdPredicate; -import mage.game.ExileZone; -import mage.game.Game; -import mage.game.Graveyard; -import mage.game.Table; +import mage.game.*; import mage.game.combat.CombatGroup; import mage.game.command.CommandObject; import mage.game.events.DamagePlayerEvent; @@ -2769,7 +2766,6 @@ public abstract class PlayerImpl implements Player, Serializable { * Used to mark the playable cards in GameView * * @return A Set of cardIds that are playable - * @see mage.server.GameSessionPlayer#getGameView() * * @param game * @@ -3153,87 +3149,24 @@ public abstract class PlayerImpl implements Player, Serializable { successfulMovedCards = moveCardsToGraveyardWithInfo(cards, source, game, fromZone); return successfulMovedCards.size() > 0; case BATTLEFIELD: // new logic that does not yet add the permanents to battlefield while replacement effects are handled - List permanents = new ArrayList<>(); - List permanentsEntered = new ArrayList<>(); - // Move meld pieces instead of the meld card if unmelded - Set meldPiecesToAdd = new HashSet<>(0); - Set meldCardsRemoved = new HashSet<>(0); - for (Iterator it = cards.iterator(); it.hasNext();) { - Card card = it.next(); - if (card instanceof MeldCard && !((MeldCard) card).isMelded()) { - MeldCard meldCard = (MeldCard) card; - if (meldCard.getTopLastZoneChangeCounter() == meldCard.getTopHalfCard().getZoneChangeCounter(game)) { - meldPiecesToAdd.add(meldCard.getTopHalfCard()); - } - if (meldCard.getBottomLastZoneChangeCounter() == meldCard.getBottomHalfCard().getZoneChangeCounter(game)) { - meldPiecesToAdd.add(meldCard.getBottomHalfCard()); - } - meldCardsRemoved.add(meldCard); - it.remove(); - } - } - cards.addAll(meldPiecesToAdd); + List infoList = new ArrayList(); for (Card card : cards) { - UUID controllingPlayerId = byOwner ? card.getOwnerId() : getId(); fromZone = game.getState().getZone(card.getId()); - if (faceDown) { - card.setFaceDown(true, game); - } - ZoneChangeEvent event = new ZoneChangeEvent(card.getId(), source.getSourceId(), controllingPlayerId, fromZone, Zone.BATTLEFIELD, appliedEffects); - if (!game.replaceEvent(event)) { - // get permanent - Permanent permanent; - if (card instanceof MeldCard) { - permanent = new PermanentMeld(card, event.getPlayerId(), game);// controlling player can be replaced so use event player now - } else { - permanent = new PermanentCard(card, event.getPlayerId(), game);// controlling player can be replaced so use event player now - } - permanents.add(permanent); - game.getPermanentsEntering().put(permanent.getId(), permanent); - card.checkForCountersToAdd(permanent, game); - permanent.setTapped(tapped); - permanent.setFaceDown(faceDown, game); - } - if (faceDown) { - card.setFaceDown(false, game); - } + ZoneChangeEvent event = new ZoneChangeEvent(card.getId(), source.getSourceId(), byOwner ? card.getOwnerId() : getId(), fromZone, Zone.BATTLEFIELD, appliedEffects); + infoList.add(new ZoneChangeInfo.Battlefield(event, faceDown, tapped)); } - game.setScopeRelevant(true); - for (Permanent permanent : permanents) { - fromZone = game.getState().getZone(permanent.getId()); - // make sure the controller of all continuous effects of this card are switched to the current controller - game.getContinuousEffects().setController(permanent.getId(), permanent.getControllerId()); - if (permanent.entersBattlefield(source.getSourceId(), game, fromZone, true)) { - permanentsEntered.add(permanent); - } else { - // revert controller to owner if permanent does not enter - game.getContinuousEffects().setController(permanent.getId(), permanent.getOwnerId()); - game.getPermanentsEntering().remove(permanent.getId()); - } - } - game.setScopeRelevant(false); - for (Permanent permanent : permanentsEntered) { - fromZone = game.getState().getZone(permanent.getId()); - if (((Card) permanent).removeFromZone(game, fromZone, source.getSourceId())) { - permanent.updateZoneChangeCounter(game); - game.addPermanent(permanent); - permanent.setZone(Zone.BATTLEFIELD, game); - game.getPermanentsEntering().remove(permanent.getId()); + infoList = ZonesHandler.moveCards(infoList, game); + for (ZoneChangeInfo info : infoList) { + Permanent permanent = game.getPermanent(info.event.getTargetId()); + if (permanent != null) { successfulMovedCards.add(permanent); - game.addSimultaneousEvent(new ZoneChangeEvent(permanent, permanent.getControllerId(), fromZone, Zone.BATTLEFIELD)); if (!game.isSimulation()) { - game.informPlayers(this.getLogName() + " puts " + (faceDown ? "a card face down " : permanent.getLogName()) - + " from " + fromZone.toString().toLowerCase(Locale.ENGLISH) + " onto the Battlefield"); + game.informPlayers(game.getPlayer(info.event.getPlayerId()) + " puts " + + (info.faceDown ? "a card face down " : permanent.getLogName()) + " from " + + fromZone.toString().toLowerCase(Locale.ENGLISH) + " onto the Battlefield"); } - } else { - game.getPermanentsEntering().remove(permanent.getId()); } } - // Update the lastZoneChangeCounter of meld pieces that were moved - for (MeldCard meldCard : meldCardsRemoved) { - meldCard.setTopLastZoneChangeCounter(meldCard.getTopHalfCard().getZoneChangeCounter(game)); - meldCard.setBottomLastZoneChangeCounter(meldCard.getBottomHalfCard().getZoneChangeCounter(game)); - } game.applyEffects(); break; case HAND: From 8f0258bc0767531d8e71b08977bcd096e71d8e11 Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Thu, 8 Sep 2016 07:26:28 -0400 Subject: [PATCH 6/7] Fix two typos --- .../abilities/oneshot/counterspell/CounterbalanceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterbalanceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterbalanceTest.java index f52385a52bf..140fdaac94e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterbalanceTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/CounterbalanceTest.java @@ -77,8 +77,8 @@ public class CounterbalanceTest extends CardTestPlayerBase { } /** - * Test that if the top cardis a split card, both casting costs of the split cards - * count to counter the spell. If one of the split cards halfes has the equal casting + * Test that if the top card is a split card, both casting costs of the split cards + * count to counter the spell. If one of the split cards halves has the equal casting * cost, the spell is countered. * */ From d53f751711cf6bca5c6381b41eb6c666374dcf9d Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Thu, 8 Sep 2016 19:56:40 -0400 Subject: [PATCH 7/7] Update the cheat testing code The new Zone change code is more strict so the cheat code needs to trick it. --- Mage.Server/src/main/java/mage/server/util/SystemUtil.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java index 7893fbca3d7..4bae78ba57a 100644 --- a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java +++ b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java @@ -125,8 +125,11 @@ public class SystemUtil { * @param card Card to put to player's hand */ private static void swapWithAnyCard(Game game, Player player, Card card, Zone zone) { + // Put the card in Exile to start. Otherwise the game doesn't know where to remove the card from. + game.getExile().getPermanentExile().add(card); + game.setZone(card.getId(), Zone.EXILED); if (zone.equals(Zone.BATTLEFIELD)) { - card.putOntoBattlefield(game, Zone.OUTSIDE, null, player.getId()); + card.putOntoBattlefield(game, Zone.EXILED, null, player.getId()); } else if (zone.equals(Zone.LIBRARY)) { card.setZone(Zone.LIBRARY, game); player.getLibrary().putOnTop(card, game);