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 dec5a49741..73aba819e8 100644 --- a/Mage.Server/src/main/java/mage/server/util/SystemUtil.java +++ b/Mage.Server/src/main/java/mage/server/util/SystemUtil.java @@ -22,6 +22,7 @@ import mage.game.command.CommandObject; import mage.game.command.Plane; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.util.CardUtil; import mage.util.RandomUtil; import java.io.File; @@ -572,7 +573,7 @@ public final class SystemUtil { // move card from exile to stack for (Card card : cardsToLoad) { - swapWithAnyCard(game, player, card, Zone.STACK); + putCardToZone(game, player, card, Zone.STACK); } continue; @@ -647,7 +648,7 @@ public final class SystemUtil { } else { // as other card for (Card card : cardsToLoad) { - swapWithAnyCard(game, player, card, gameZone); + putCardToZone(game, player, card, gameZone); } } } @@ -657,38 +658,33 @@ public final class SystemUtil { } /** - * Swap cards between specified card from library and any hand card. - * - * @param game - * @param card Card to put to player's hand + * Put card to specified (battlefield zone will be tranformed to permanent with initialized effects) */ - 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); + private static void putCardToZone(Game game, Player player, Card card, Zone zone) { + // TODO: replace by player.move? switch (zone) { case BATTLEFIELD: - card.putOntoBattlefield(game, Zone.EXILED, null, player.getId()); + CardUtil.putCardOntoBattlefieldWithEffects(game, card, player); break; case LIBRARY: card.setZone(Zone.LIBRARY, game); - game.getExile().getPermanentExile().remove(card); player.getLibrary().putOnTop(card, game); break; case STACK: - card.cast(game, Zone.EXILED, card.getSpellAbility(), player.getId()); + card.cast(game, game.getState().getZone(card.getId()), card.getSpellAbility(), player.getId()); break; case EXILED: - // nothing to do + card.setZone(Zone.EXILED, game); + game.getExile().getPermanentExile().add(card); break; case OUTSIDE: card.setZone(Zone.OUTSIDE, game); - game.getExile().getPermanentExile().remove(card); break; default: card.moveToZone(zone, null, game, false); break; } + game.applyEffects(); logger.info("Added card to player's " + zone.toString() + ": " + card.getName() + ", player = " + player.getName()); } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 48604e957e..eebae0d716 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1,9 +1,5 @@ package mage.game; -import java.io.IOException; -import java.io.Serializable; -import java.util.*; -import java.util.Map.Entry; import mage.MageException; import mage.MageObject; import mage.abilities.*; @@ -64,6 +60,7 @@ import mage.target.Target; import mage.target.TargetCard; import mage.target.TargetPermanent; import mage.target.TargetPlayer; +import mage.util.CardUtil; import mage.util.GameLog; import mage.util.MessageToClient; import mage.util.RandomUtil; @@ -71,6 +68,11 @@ import mage.util.functions.ApplyToPermanent; import mage.watchers.common.*; import org.apache.log4j.Logger; +import java.io.IOException; +import java.io.Serializable; +import java.util.*; +import java.util.Map.Entry; + public abstract class GameImpl implements Game, Serializable { private static final int ROLLBACK_TURNS_MAX = 4; @@ -1805,7 +1807,7 @@ public abstract class GameImpl implements Game, Serializable { break; } // triggered abilities that don't use the stack have to be executed first (e.g. Banisher Priest Return exiled creature - for (Iterator it = abilities.iterator(); it.hasNext();) { + for (Iterator it = abilities.iterator(); it.hasNext(); ) { TriggeredAbility triggeredAbility = it.next(); if (!triggeredAbility.isUsesStack()) { state.removeTriggeredAbility(triggeredAbility); @@ -1880,7 +1882,7 @@ public abstract class GameImpl implements Game, Serializable { Zone currentZone = this.getState().getZone(card.getId()); String currentZoneInfo = (currentZone == null ? "(error)" : "(" + currentZone.name() + ")"); if (player.chooseUse(Outcome.Benefit, "Move " + card.getIdName() - + " to the command zone or leave it in current zone " + currentZoneInfo + "?", "You can only make this choice once per object", + + " to the command zone or leave it in current zone " + currentZoneInfo + "?", "You can only make this choice once per object", "Move to command", "Leave in current zone " + currentZoneInfo, null, this)) { toMove.add(card); } else { @@ -2602,7 +2604,7 @@ public abstract class GameImpl implements Game, Serializable { } //20100423 - 800.4a Set toOutside = new HashSet<>(); - for (Iterator it = getBattlefield().getAllPermanents().iterator(); it.hasNext();) { + for (Iterator it = getBattlefield().getAllPermanents().iterator(); it.hasNext(); ) { Permanent perm = it.next(); if (perm.isOwnedBy(playerId)) { if (perm.getAttachedTo() != null) { @@ -2652,7 +2654,7 @@ public abstract class GameImpl implements Game, Serializable { player.moveCards(toOutside, Zone.OUTSIDE, null, this); // triggered abilities that don't use the stack have to be executed List abilities = state.getTriggered(player.getId()); - for (Iterator it = abilities.iterator(); it.hasNext();) { + for (Iterator it = abilities.iterator(); it.hasNext(); ) { TriggeredAbility triggeredAbility = it.next(); if (!triggeredAbility.isUsesStack()) { state.removeTriggeredAbility(triggeredAbility); @@ -2672,7 +2674,7 @@ public abstract class GameImpl implements Game, Serializable { // Remove cards from the player in all exile zones for (ExileZone exile : this.getExile().getExileZones()) { - for (Iterator it = exile.iterator(); it.hasNext();) { + for (Iterator it = exile.iterator(); it.hasNext(); ) { Card card = this.getCard(it.next()); if (card != null && card.isOwnedBy(playerId)) { it.remove(); @@ -2682,7 +2684,7 @@ public abstract class GameImpl implements Game, Serializable { //Remove all commander/emblems/plane the player controls boolean addPlaneAgain = false; - for (Iterator it = this.getState().getCommand().iterator(); it.hasNext();) { + for (Iterator it = this.getState().getCommand().iterator(); it.hasNext(); ) { CommandObject obj = it.next(); if (obj.isControlledBy(playerId)) { if (obj instanceof Emblem) { @@ -3063,28 +3065,10 @@ public abstract class GameImpl implements Game, Serializable { throw new IllegalArgumentException("Command zone supports in commander test games"); } - // warning, permanents go to battlefield without resolve, continuus effects must be init for (PermanentCard permanentCard : battlefield) { - permanentCard.setZone(Zone.BATTLEFIELD, this); - permanentCard.setOwnerId(ownerId); - PermanentCard newPermanent = new PermanentCard(permanentCard.getCard(), ownerId, this); - getPermanentsEntering().put(newPermanent.getId(), newPermanent); - newPermanent.entersBattlefield(newPermanent.getId(), this, Zone.OUTSIDE, false); - addPermanent(newPermanent, getState().getNextPermanentOrderNumber()); - getPermanentsEntering().remove(newPermanent.getId()); - newPermanent.removeSummoningSickness(); - if (permanentCard.isTapped()) { - newPermanent.setTapped(true); - } - - // init effects on static abilities (init continuous effects, warning, game state contains copy) - for (ContinuousEffect effect : this.getState().getContinuousEffects().getLayeredEffects(this)) { - Optional ability = this.getState().getContinuousEffects().getLayeredEffectAbilities(effect).stream().findFirst(); - if (ability.isPresent() && newPermanent.getId().equals(ability.get().getSourceId())) { - effect.init(ability.get(), this, activePlayerId); // game is not setup yet, game.activePlayer is null -- need direct id - } - } + CardUtil.putCardOntoBattlefieldWithEffects(this, permanentCard, player); } + applyEffects(); } } diff --git a/Mage/src/main/java/mage/game/ZonesHandler.java b/Mage/src/main/java/mage/game/ZonesHandler.java index 81f6a57c4d..1964e08b47 100644 --- a/Mage/src/main/java/mage/game/ZonesHandler.java +++ b/Mage/src/main/java/mage/game/ZonesHandler.java @@ -1,6 +1,5 @@ package mage.game; -import java.util.*; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; @@ -19,6 +18,8 @@ import mage.game.stack.Spell; import mage.players.Player; import mage.target.TargetCard; +import java.util.*; + /** * Created by samuelsandeen on 9/6/16. */ @@ -52,7 +53,7 @@ public final class ZonesHandler { public static List moveCards(List zoneChangeInfos, Game game) { // Handle Unmelded Meld Cards - for (ListIterator itr = zoneChangeInfos.listIterator(); itr.hasNext();) { + 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. @@ -201,7 +202,7 @@ public final class ZonesHandler { 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();) { + for (Iterator itr = unmelded.subInfo.iterator(); itr.hasNext(); ) { ZoneChangeInfo subInfo = itr.next(); if (!maybeRemoveFromSourceZone(subInfo, game)) { itr.remove(); @@ -233,6 +234,7 @@ public final class ZonesHandler { if (!game.replaceEvent(event)) { Zone fromZone = event.getFromZone(); if (event.getToZone() == Zone.BATTLEFIELD) { + // prepare card and permanent // 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 @@ -245,15 +247,18 @@ public final class ZonesHandler { } else { permanent = new PermanentCard(card, event.getPlayerId(), game); } + + // put onto battlefield with possible counters 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); + 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()); diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 9faaf31d91..47680901cf 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -8,18 +8,20 @@ import mage.abilities.Mode; import mage.abilities.SpellAbility; import mage.abilities.costs.VariableCost; import mage.abilities.costs.mana.*; +import mage.abilities.effects.ContinuousEffect; import mage.cards.Card; -import mage.constants.ColoredManaSymbol; -import mage.constants.EmptyNames; -import mage.constants.ManaType; -import mage.constants.SpellAbilityType; +import mage.cards.MeldCard; +import mage.constants.*; import mage.filter.Filter; import mage.filter.predicate.mageobject.NamePredicate; import mage.game.CardState; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; +import mage.game.permanent.PermanentMeld; import mage.game.permanent.token.Token; import mage.game.stack.Spell; +import mage.players.Player; import mage.target.Target; import mage.util.functions.CopyTokenFunction; @@ -829,4 +831,48 @@ public final class CardUtil { .flatMap(Collection::stream) .collect(Collectors.toSet()); } + + /** + * Put card to battlefield without resolve + * + * @param game + * @param card + * @param player + */ + public static void putCardOntoBattlefieldWithEffects(Game game, Card card, Player player) { + // same logic as ZonesHandler->maybeRemoveFromSourceZone + + // prepare card and permanent + card.setZone(Zone.BATTLEFIELD, game); + card.setOwnerId(player.getId()); + PermanentCard permanent; + if (card instanceof MeldCard) { + permanent = new PermanentMeld(card, player.getId(), game); + } else { + permanent = new PermanentCard(card, player.getId(), game); + } + + // put onto battlefield with possible counters + game.getPermanentsEntering().put(permanent.getId(), permanent); + card.checkForCountersToAdd(permanent, game); + permanent.entersBattlefield(permanent.getId(), game, Zone.OUTSIDE, false); + game.addPermanent(permanent, game.getState().getNextPermanentOrderNumber()); + game.getPermanentsEntering().remove(permanent.getId()); + + // workaround for special tapped status from test framework's command (addCard) + if (card instanceof PermanentCard && ((PermanentCard) card).isTapped()) { + permanent.setTapped(true); + } + + // remove sickness + permanent.removeSummoningSickness(); + + // init effects on static abilities (init continuous effects; warning, game state contains copy) + for (ContinuousEffect effect : game.getState().getContinuousEffects().getLayeredEffects(game)) { + Optional ability = game.getState().getContinuousEffects().getLayeredEffectAbilities(effect).stream().findFirst(); + if (ability.isPresent() && permanent.getId().equals(ability.get().getSourceId())) { + effect.init(ability.get(), game, player.getId()); + } + } + } }