diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 260ede11f3f..465f6c86b91 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -446,7 +446,7 @@ public class AiController { CardCollection nonLandsInHand = CardLists.filter(player.getCardsIn(ZoneType.Hand), Predicates.not(CardPredicates.Presets.LANDS)); // Some considerations for Momir/MoJhoSto - boolean hasMomir = player.getZone(ZoneType.Command).contains(CardPredicates.nameEquals("Momir Vig, Simic Visionary Avatar")); + boolean hasMomir = player.isCardInCommand("Momir Vig, Simic Visionary Avatar"); if (hasMomir && nonLandsInHand.isEmpty()) { // Only do this if we have an all-basic land hand, which covers both stock Momir and MoJhoSto modes // and also a custom Vanguard setup with a customized basic land deck and Momir as the avatar. @@ -1534,7 +1534,7 @@ public class AiController { } private boolean isSafeToHoldLandDropForMain2(Card landToPlay) { - boolean hasMomir = player.getZone(ZoneType.Command).contains(CardPredicates.nameEquals("Momir Vig, Simic Visionary Avatar")); + boolean hasMomir = player.isCardInCommand("Momir Vig, Simic Visionary Avatar"); if (hasMomir) { // Don't do this in Momir variants since it messes with the AI decision making for the avatar. return false; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index e286ef8efb8..3e7def1b3a1 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -2054,7 +2054,7 @@ public class ComputerUtil { return finalHandSize; } - CardCollectionView library = ai.getZone(ZoneType.Library).getCards(); + CardCollectionView library = ai.getCardsIn(ZoneType.Library); int landsInDeck = CardLists.count(library, CardPredicates.isType("Land")); // no land deck, can't do anything better diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index 2abe0ccd427..d677c7fc8f0 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -200,13 +200,13 @@ public abstract class GameState { // Mark the cards that need their ID remembered for various reasons cardsReferencedByID.clear(); for (ZoneType zone : ZONES.keySet()) { - for (Card card : game.getCardsIn(zone)) { + for (Card card : game.getCardsIncludePhasingIn(zone)) { if (card.getExiledWith() != null) { // Remember the ID of the card that exiled this card cardsReferencedByID.add(card.getExiledWith()); } if (zone == ZoneType.Battlefield) { - if (card.hasCardAttachments()) { + if (!card.getAllAttachedCards().isEmpty()) { // Remember the ID of cards that have attachments cardsReferencedByID.add(card); } @@ -240,7 +240,7 @@ public abstract class GameState { // if the zone had no cards in it (e.g. empty hand). aiCardTexts.put(zone, ""); humanCardTexts.put(zone, ""); - for (Card card : game.getCardsIn(zone)) { + for (Card card : game.getCardsIncludePhasingIn(zone)) { if (card.getName().equals("Puzzle Goal") && card.getOracleText().contains("New Puzzle")) { puzzleCreatorState = true; } @@ -264,7 +264,7 @@ public abstract class GameState { return; } - if (!c.getMergedCards().isEmpty()) { + if (c.hasMergedCard()) { // we have to go by the current top card name here newText.append(c.getTopMergedCard().getPaperCard().getName()); } else { @@ -297,7 +297,8 @@ public abstract class GameState { newText.append("|Monstrous"); } if (c.isPhasedOut()) { - newText.append("|PhasedOut"); + newText.append("|PhasedOut:"); + newText.append(c.getPhasedOut().isAI() ? "AI" : "HUMAN"); } if (c.isFaceDown()) { newText.append("|FaceDown"); @@ -1328,7 +1329,10 @@ public abstract class GameState { } else if (info.startsWith("Monstrous")) { c.setMonstrous(true); } else if (info.startsWith("PhasedOut")) { - c.setPhasedOut(true); + String tgt = info.substring(info.indexOf(':') + 1); + Player human = player.getGame().getPlayers().get(0); + Player ai = player.getGame().getPlayers().get(1); + c.setPhasedOut(tgt.equalsIgnoreCase("AI") ? ai : human); } else if (info.startsWith("Counters:")) { applyCountersToGameEntity(c, info.substring(info.indexOf(':') + 1)); } else if (info.startsWith("SummonSick")) { diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 12f9c33d484..57d50d3a087 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -797,7 +797,7 @@ public class PlayerControllerAi extends PlayerController { case "Never": return false; case "NothingRemembered": - if (source.getRememberedCount() == 0) { + if (!source.hasRemembered()) { return true; } else { Card rem = (Card) source.getFirstRemembered(); @@ -807,7 +807,7 @@ public class PlayerControllerAi extends PlayerController { } break; case "BetterTgtThanRemembered": - if (source.getRememberedCount() > 0) { + if (source.hasRemembered()) { Card rem = (Card) source.getFirstRemembered(); // avoid pumping opponent creature if (!rem.isInPlay() || rem.getController().isOpponentOf(source.getController())) { diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 8ab2a0474bc..59506328d64 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -559,7 +559,7 @@ public class Game { CardCollection cards = new CardCollection(); for (final Player p : getPlayers()) { - cards.addAll(p.getCardsIncludePhasingIn(zone)); + cards.addAll(p.getCardsIn(zone, false)); } return cards; } diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index 1ce87661d78..1f1fa1380d1 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -419,10 +419,16 @@ public abstract class SpellAbilityEffect { protected static void addForgetCounterTrigger(final Card card, final String counterType) { String trig = "Mode$ CounterRemoved | TriggerZones$ Command | ValidCard$ Card.IsRemembered | CounterType$ " + counterType + " | NewCounterAmount$ 0 | Static$ True"; + String trig2 = "Mode$ PhaseOut | TriggerZones$ Command | ValidCard$ Card.phasedOutIsRemembered | Static$ True"; + + final SpellAbility forgetSA = getForgetSpellAbility(card); final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true); - parsedTrigger.setOverridingAbility(getForgetSpellAbility(card)); + final Trigger parsedTrigger2 = TriggerHandler.parseTrigger(trig2, card, true); + parsedTrigger.setOverridingAbility(forgetSA); + parsedTrigger2.setOverridingAbility(forgetSA); card.addTrigger(parsedTrigger); + card.addTrigger(parsedTrigger2); } protected static void addLeaveBattlefieldReplacement(final Card card, final SpellAbility sa, final String zone) { @@ -764,6 +770,9 @@ public abstract class SpellAbilityEffect { } protected static void addUntilCommand(final SpellAbility sa, GameCommand until) { + addUntilCommand(sa, until, sa.getActivatingPlayer()); + } + protected static void addUntilCommand(final SpellAbility sa, GameCommand until, Player controller) { Card host = sa.getHostCard(); final Game game = host.getGame(); final String duration = sa.getParam("Duration"); @@ -774,19 +783,25 @@ public abstract class SpellAbilityEffect { if ("UntilEndOfCombat".equals(duration)) { game.getEndOfCombat().addUntil(until); + } else if ("UntilEndOfCombatYourNextTurn".equals(duration)) { + game.getEndOfCombat().registerUntilEnd(controller, until); } else if ("UntilYourNextUpkeep".equals(duration)) { - game.getUpkeep().addUntil(sa.getActivatingPlayer(), until); + game.getUpkeep().addUntil(controller, until); } else if ("UntilTheEndOfYourNextUpkeep".equals(duration)) { if (game.getPhaseHandler().is(PhaseType.UPKEEP)) { - game.getUpkeep().registerUntilEnd(host.getController(), until); + game.getUpkeep().registerUntilEnd(controller, until); } else { - game.getUpkeep().addUntilEnd(host.getController(), until); + game.getUpkeep().addUntilEnd(controller, until); } - } else if ("UntilTheEndOfYourNextTurn".equals(duration)) { - if (game.getPhaseHandler().isPlayerTurn(sa.getActivatingPlayer())) { - game.getEndOfTurn().registerUntilEnd(sa.getActivatingPlayer(), until); + } else if ("UntilYourNextEndStep".equals(duration)) { + game.getEndOfTurn().addUntil(controller, until); + } else if ("UntilYourNextTurn".equals(duration)) { + game.getCleanup().addUntil(controller, until); + } else if ("UntilTheEndOfYourNextTurn".equals(duration)) { + if (game.getPhaseHandler().isPlayerTurn(controller)) { + game.getEndOfTurn().registerUntilEnd(controller, until); } else { - game.getEndOfTurn().addUntilEnd(sa.getActivatingPlayer(), until); + game.getEndOfTurn().addUntilEnd(controller, until); } } else if ("UntilTheEndOfTargetedNextTurn".equals(duration)) { Player targeted = sa.getTargets().getFirstTargetedPlayer(); @@ -795,6 +810,17 @@ public abstract class SpellAbilityEffect { } else { game.getEndOfTurn().addUntilEnd(targeted, until); } + } else if ("ThisTurnAndNextTurn".equals(duration)) { + game.getEndOfTurn().addUntil(new GameCommand() { + private static final long serialVersionUID = -5054153666503075717L; + + @Override + public void run() { + game.getEndOfTurn().addUntil(until); + } + }); + } else if ("UntilStateBasedActionChecked".equals(duration)) { + game.addSBACheckedCommand(until); } else if (duration != null && duration.startsWith("UntilAPlayerCastSpell")) { game.getStack().addCastCommand(duration.split(" ")[1], until); } else if ("UntilHostLeavesPlay".equals(duration)) { @@ -805,9 +831,8 @@ public abstract class SpellAbilityEffect { } else if ("UntilLoseControlOfHost".equals(duration)) { host.addLeavesPlayCommand(until); host.addChangeControllerCommand(until); - } else if ("UntilYourNextTurn".equals(duration)) { - game.getCleanup().addUntil(sa.getActivatingPlayer(), until); } else if ("UntilUntaps".equals(duration)) { + host.addLeavesPlayCommand(until); host.addUntapCommand(until); } else if ("UntilUnattached".equals(duration)) { host.addLeavesPlayCommand(until); //if it leaves play, it's unattached diff --git a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java index 80f3544731d..51da7b83dc2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java @@ -55,13 +55,16 @@ public class EffectEffect extends SpellAbilityEffect { List effectOwner = null; final String duration = sa.getParam("Duration"); - if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration)) + if (((duration != null && duration.startsWith("UntilHostLeavesPlay")) || "UntilLoseControlOfHost".equals(duration) || "UntilUntaps".equals(duration)) && !(hostCard.isInPlay() || hostCard.isInZone(ZoneType.Stack))) { return; } if ("UntilLoseControlOfHost".equals(duration) && hostCard.getController() != sa.getActivatingPlayer()) { return; } + if ("UntilUntaps".equals(duration) && !hostCard.isTapped()) { + return; + } if (sa.hasParam("Abilities")) { effectAbilities = sa.getParam("Abilities").split(","); @@ -94,7 +97,7 @@ public class EffectEffect extends SpellAbilityEffect { if (sa.hasParam("ForgetCounter")) { CounterType cType = CounterType.getType(sa.getParam("ForgetCounter")); - rememberList = new FCollection(CardLists.filter(Iterables.filter(rememberList, Card.class), CardPredicates.hasCounter(cType))); + rememberList = new FCollection<>(CardLists.filter(Iterables.filter(rememberList, Card.class), CardPredicates.hasCounter(cType))); } // don't create Effect if there is no remembered Objects @@ -294,7 +297,6 @@ public class EffectEffect extends SpellAbilityEffect { registerDelayedTrigger(sa, sa.getParam("AtEOT"), Lists.newArrayList(hostCard)); } - // Duration if (duration == null || !duration.equals("Permanent")) { final GameCommand endEffect = new GameCommand() { private static final long serialVersionUID = -5861759814760561373L; @@ -305,44 +307,7 @@ public class EffectEffect extends SpellAbilityEffect { } }; - if (duration == null || duration.equals("EndOfTurn")) { - game.getEndOfTurn().addUntil(endEffect); - } else if (duration.equals("UntilHostLeavesPlay")) { - hostCard.addLeavesPlayCommand(endEffect); - } else if (duration.equals("UntilHostLeavesPlayOrEOT")) { - game.getEndOfTurn().addUntil(endEffect); - hostCard.addLeavesPlayCommand(endEffect); - } else if (duration.equals("UntilLoseControlOfHost")) { - hostCard.addLeavesPlayCommand(endEffect); - hostCard.addChangeControllerCommand(endEffect); - } else if (duration.equals("UntilYourNextTurn")) { - game.getCleanup().addUntil(controller, endEffect); - } else if (duration.equals("UntilYourNextUpkeep")) { - game.getUpkeep().addUntil(controller, endEffect); - } else if (duration.equals("UntilEndOfCombat")) { - game.getEndOfCombat().addUntil(endEffect); - } else if (duration.equals("UntilEndOfCombatYourNextTurn")) { - game.getEndOfCombat().registerUntilEnd(controller, endEffect); - } else if (duration.equals("UntilYourNextEndStep")) { - game.getEndOfTurn().addUntil(controller, endEffect); - } else if (duration.equals("UntilTheEndOfYourNextTurn")) { - if (game.getPhaseHandler().isPlayerTurn(controller)) { - game.getEndOfTurn().registerUntilEnd(controller, endEffect); - } else { - game.getEndOfTurn().addUntilEnd(controller, endEffect); - } - } else if (duration.equals("ThisTurnAndNextTurn")) { - game.getEndOfTurn().addUntil(new GameCommand() { - private static final long serialVersionUID = -5054153666503075717L; - - @Override - public void run() { - game.getEndOfTurn().addUntil(endEffect); - } - }); - } else if (duration.equals("UntilStateBasedActionChecked")) { - game.addSBACheckedCommand(endEffect); - } + addUntilCommand(sa, endEffect, controller); } if (sa.hasParam("ImprintOnHost")) { diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 63904d99354..fe0c36e6c23 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -233,7 +233,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { // set for transform and meld, needed for clone effects private boolean backside = false; - private boolean phasedOut = false; + private Player phasedOut; private boolean directlyPhasedOut = true; private boolean wontPhaseInNormal = false; @@ -4998,9 +4998,15 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } public final boolean isPhasedOut() { + return phasedOut != null; + } + public final boolean isPhasedOut(Player turn) { + return turn.equals(phasedOut); + } + public final Player getPhasedOut() { return phasedOut; } - public final void setPhasedOut(final boolean phasedOut0) { + public final void setPhasedOut(final Player phasedOut0) { if (phasedOut == phasedOut0) { return; } phasedOut = phasedOut0; view.updatePhasedOut(this); @@ -5040,15 +5046,15 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } private boolean switchPhaseState(final boolean fromUntapStep) { - if (phasedOut && StaticAbilityCantPhaseIn.cantPhaseIn(this)) { + if (isPhasedOut() && StaticAbilityCantPhaseIn.cantPhaseIn(this)) { return false; } - if (!phasedOut && StaticAbilityCantPhaseOut.cantPhaseOut(this)) { + if (!isPhasedOut() && StaticAbilityCantPhaseOut.cantPhaseOut(this)) { return false; } - if (phasedOut && fromUntapStep && wontPhaseInNormal) { + if (isPhasedOut() && fromUntapStep && wontPhaseInNormal) { return false; } @@ -5061,16 +5067,23 @@ public class Card extends GameEntity implements Comparable, IHasSVars { // when it doesn't exist the game will no longer see it as tapped runUntapCommands(); // TODO CR 702.26f need to run LeavesPlay + changeController commands but only when worded "for as long as" + + // these links also break + clearEncodedCards(); + if (isPaired()) { + getPairedWith().setPairedWith(null); + setPairedWith(null); + } } - setPhasedOut(!phasedOut); + setPhasedOut(isPhasedOut() ? null : getController()); final Combat combat = getGame().getCombat(); - if (combat != null && phasedOut) { + if (combat != null && isPhasedOut()) { combat.saveLKI(this); combat.removeFromCombat(this); } - if (!phasedOut) { + if (!isPhasedOut()) { // Just phased in, time to run the phased in trigger getGame().getTriggerHandler().registerActiveTrigger(this, false); getGame().getTriggerHandler().runTrigger(TriggerType.PhaseIn, runParams, false); diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index dd5f4a5020f..b9492342cee 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -239,7 +239,7 @@ public final class CardUtil { newCopy.setCounters(Maps.newHashMap(in.getCounters())); newCopy.setColor(in.getColor().getColor()); - newCopy.setPhasedOut(in.isPhasedOut()); + newCopy.setPhasedOut(in.getPhasedOut()); newCopy.setDamageHistory(in.getDamageHistory()); newCopy.setDamageReceivedThisTurn(in.getDamageReceivedThisTurn()); diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index 71c8c44a975..ea61da97ce0 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -192,7 +192,7 @@ public class PhaseHandler implements java.io.Serializable { game.fireEvent(new GameEventTurnBegan(playerTurn, turn)); // Tokens starting game in play should suffer from Sum. Sickness - for (final Card c : playerTurn.getCardsIncludePhasingIn(ZoneType.Battlefield)) { + for (final Card c : playerTurn.getCardsIn(ZoneType.Battlefield, false)) { if (playerTurn.getTurn() > 0 || !c.isStartsGameInPlay()) { c.setSickness(false); } @@ -201,8 +201,8 @@ public class PhaseHandler implements java.io.Serializable { game.getAction().resetActivationsPerTurn(); - final List lands = CardLists.filter(playerTurn.getLandsInPlay(), Presets.UNTAPPED); - playerTurn.setNumPowerSurgeLands(lands.size()); + final int lands = CardLists.count(playerTurn.getLandsInPlay(), Presets.UNTAPPED); + playerTurn.setNumPowerSurgeLands(lands); } //update tokens game.fireEvent(new GameEventTokenStateUpdate(playerTurn.getTokensInPlay())); diff --git a/forge-game/src/main/java/forge/game/phase/Untap.java b/forge-game/src/main/java/forge/game/phase/Untap.java index 0ced0a3acfd..dcdd3861e09 100644 --- a/forge-game/src/main/java/forge/game/phase/Untap.java +++ b/forge-game/src/main/java/forge/game/phase/Untap.java @@ -253,11 +253,11 @@ public class Untap extends Phase { private static void doPhasing(final Player turn) { // Needs to include phased out cards - final List list = CardLists.filter(turn.getCardsIncludePhasingIn(ZoneType.Battlefield), new Predicate() { + final List list = CardLists.filter(turn.getGame().getCardsIncludePhasingIn(ZoneType.Battlefield), new Predicate() { @Override public boolean apply(final Card c) { - return (c.isPhasedOut() && c.isDirectlyPhasedOut()) || c.hasKeyword(Keyword.PHASING); + return (c.isPhasedOut(turn) && c.isDirectlyPhasedOut()) || (c.hasKeyword(Keyword.PHASING) && c.getController().equals(turn)); } }); diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 0cb7a39da1a..922af3691e2 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1307,10 +1307,6 @@ public class Player extends GameEntity implements Comparable { return zone == null ? CardCollection.EMPTY : zone.getCards(filterOutPhasedOut); } - public final CardCollectionView getCardsIncludePhasingIn(final ZoneType zone) { - return getCardsIn(zone, false); - } - /** * gets a list of first N cards in the requested zone. This function makes a CardCollectionView from Card[]. */ @@ -2357,9 +2353,8 @@ public class Player extends GameEntity implements Comparable { } public CardCollectionView getColoredCardsInPlay(final String color) { - return CardLists.getColor(getCardsIn(ZoneType.Battlefield), MagicColor.fromName(color)); + return getColoredCardsInPlay(MagicColor.fromName(color)); } - public CardCollectionView getColoredCardsInPlay(final byte color) { return CardLists.getColor(getCardsIn(ZoneType.Battlefield), color); } @@ -2631,7 +2626,7 @@ public class Player extends GameEntity implements Comparable { public final void resetCombatantsThisCombat() { // resets the status of attacked/blocked this phase - CardCollectionView list = getCardsIn(ZoneType.Battlefield); + CardCollectionView list = getCardsIn(ZoneType.Battlefield, false); for (Card c : list) { if (c.getDamageHistory().getCreatureAttackedThisCombat() > 0) { diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index 0ee21b66df9..aa23631b709 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -200,10 +200,10 @@ public class ReplacementHandler { if (!replacementEffect.hasRun() && (layer == null || replacementEffect.getLayer() == layer) && event.equals(replacementEffect.getMode()) - && replacementEffect.requirementsCheck(game) - && replacementEffect.canReplace(runParams) && !possibleReplacers.contains(replacementEffect) - && replacementEffect.zonesCheck(cardZone)) { + && replacementEffect.zonesCheck(cardZone) + && replacementEffect.requirementsCheck(game) + && replacementEffect.canReplace(runParams)) { possibleReplacers.add(replacementEffect); } }