From c33a731a4abe157735f6462048fd572095a3a374 Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Wed, 7 Sep 2016 23:31:26 -0400 Subject: [PATCH] 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: