package mage.game; import java.util.*; 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; /** * Created by samuelsandeen on 9/6/16. */ public final 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<ZoneChangeInfo> list = new ArrayList<>(); list.add(info); return !moveCards(list, game).isEmpty(); } public static List<ZoneChangeInfo> moveCards(List<ZoneChangeInfo> zoneChangeInfos, Game game) { // Handle Unmelded Meld Cards for (ListIterator<ZoneChangeInfo> 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); } } } zoneChangeInfos.removeIf(zoneChangeInfo -> !maybeRemoveFromSourceZone(zoneChangeInfo, game)); for (ZoneChangeInfo zoneChangeInfo : zoneChangeInfos) { placeInDestinationZone(zoneChangeInfo, game); if (game.getPhase() != null) { // moving cards to zones before game started does not need events 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); } // We arbitrarily prefer the bottom half card. This should never be relevant. if (toZone != null) { game.setZone(unmelded.event.getTargetId(), toZone); } return; } // Handle normal cases ZoneChangeEvent event = info.event; Zone toZone = event.getToZone(); Card targetCard = getTargetCard(game, event.getTargetId()); Cards cards = null; // If we're moving a token it shouldn't be put into any zone as an object. if (!(targetCard instanceof Permanent) && targetCard != null) { 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 && cards != null) { if (event.getToZone() != Zone.BATTLEFIELD) { ((MeldCard) targetCard).setMelded(false); } for (Card card : cards.getCards(game)) { game.setZone(card.getId(), event.getToZone()); } } } private static Card getTargetCard(Game game, UUID targetId) { if (game.getCard(targetId) != null) { return game.getCard(targetId); } if (game.getMeldCard(targetId) != null) { return game.getMeldCard(targetId); } if (game.getPermanent(targetId) != null) { return game.getPermanent(targetId); } return null; } 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<ZoneChangeInfo> itr = unmelded.subInfo.iterator(); itr.hasNext();) { ZoneChangeInfo subInfo = itr.next(); if (!maybeRemoveFromSourceZone(subInfo, game)) { itr.remove(); } else if (Objects.equals(subInfo.event.getTargetId(), meld.getTopHalfCard().getId())) { meld.setTopLastZoneChangeCounter(meld.getTopHalfCard().getZoneChangeCounter(game)); } else if (Objects.equals(subInfo.event.getTargetId(), meld.getBottomHalfCard().getId())) { meld.setBottomLastZoneChangeCounter(meld.getBottomHalfCard().getZoneChangeCounter(game)); } } if (unmelded.subInfo.isEmpty()) { return false; } // We arbitrarily prefer the bottom half card. This should never be relevant. meld.updateZoneChangeCounter(game, unmelded.subInfo.get(unmelded.subInfo.size() - 1).event); return true; } // Handle all normal cases ZoneChangeEvent event = info.event; Card card = getTargetCard(game, event.getTargetId()); if (card == null) { // If we can't find the card we can't remove it. return false; } boolean success = false; if (info.faceDown) { card.setFaceDown(true, game); } if (!game.replaceEvent(event)) { Zone fromZone = event.getFromZone(); if (event.getToZone() == Zone.BATTLEFIELD) { // If needed take attributes from the spell (e.g. color of spell was changed) card = takeAttributesFromSpell(card, event, game); // controlling player can be replaced so use event player now Permanent permanent; if (card instanceof MeldCard) { permanent = new PermanentMeld(card, event.getPlayerId(), game); } else if (card instanceof Permanent) { // This should never happen. permanent = (Permanent) card; } 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, event); } else if (!(card instanceof Permanent)) { card.updateZoneChangeCounter(game, event); } } return success; } public static List<Card> chooseOrder(String message, Cards cards, Player player, Game game) { List<Card> 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; } private static Card takeAttributesFromSpell(Card card, ZoneChangeEvent event, Game game) { card = card.copy(); if (Zone.STACK == event.getFromZone()) { Spell spell = game.getStack().getSpell(event.getTargetId()); if (spell != null && !spell.isFaceDown(game)) { if (!card.getColor(game).equals(spell.getColor(game))) { // the card that is referenced to in the permanent is copied and the spell attributes are set to this copied card card.getColor(game).setColor(spell.getColor(game)); } } } return card; } }