From 3d08e16ab9c655c82b71b4154885a4f4d737019e Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 27 Feb 2024 13:25:39 +0100 Subject: [PATCH] CantExile: add checks for Effects and Costs (#4632) --- .../main/java/forge/ai/AiCostDecision.java | 4 +- .../src/main/java/forge/ai/ComputerUtil.java | 7 +-- .../java/forge/ai/ability/ChangeZoneAi.java | 2 +- forge-game/src/main/java/forge/game/Game.java | 31 ------------ .../src/main/java/forge/game/GameAction.java | 48 +++++++++++++------ .../main/java/forge/game/GameActionUtil.java | 6 +-- .../game/ability/SpellAbilityEffect.java | 34 ++++++------- .../game/ability/effects/AddTurnEffect.java | 8 +--- .../ability/effects/ChangeZoneAllEffect.java | 3 ++ .../ability/effects/ChangeZoneEffect.java | 9 ++++ .../game/ability/effects/CounterEffect.java | 3 ++ .../effects/DamagePreventEffectBase.java | 10 +--- .../forge/game/ability/effects/DigEffect.java | 9 +++- .../ability/effects/DigMultipleEffect.java | 5 +- .../game/ability/effects/DraftEffect.java | 5 +- .../game/ability/effects/EffectEffect.java | 22 +-------- .../forge/game/ability/effects/FogEffect.java | 18 +------ .../game/ability/effects/MeldEffect.java | 10 ++-- .../game/ability/effects/MutateEffect.java | 2 +- .../game/ability/effects/PlayEffect.java | 24 ++-------- .../ability/effects/RegenerateBaseEffect.java | 19 +------- .../ability/effects/ReplaceDamageEffect.java | 2 +- .../effects/ReplaceSplitDamageEffect.java | 2 +- .../ability/effects/SetInMotionEffect.java | 7 +-- .../game/ability/effects/SkipPhaseEffect.java | 24 ++-------- .../game/ability/effects/SkipTurnEffect.java | 8 +--- .../game/ability/effects/VentureEffect.java | 4 +- .../src/main/java/forge/game/card/Card.java | 12 +++++ .../java/forge/game/card/CardPredicates.java | 9 ++++ .../forge/game/cost/CostCollectEvidence.java | 3 +- .../main/java/forge/game/cost/CostExile.java | 5 +- .../java/forge/game/phase/PhaseHandler.java | 2 +- .../main/java/forge/game/player/Player.java | 13 +++-- .../game/spellability/AbilityManaPart.java | 5 +- .../staticability/StaticAbilityCantExile.java | 42 ++++++++++++++++ .../upcoming/the_master_multiplied.txt | 9 ++++ .../java/forge/player/HumanCostDecision.java | 6 ++- 37 files changed, 203 insertions(+), 229 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/staticability/StaticAbilityCantExile.java create mode 100644 forge-gui/res/cardsfolder/upcoming/the_master_multiplied.txt diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index c6504bbae79..8ac80bfbd7d 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -59,7 +59,7 @@ public class AiCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(CostCollectEvidence cost) { int c = cost.getAbilityAmount(ability); - CardCollectionView chosen = ComputerUtil.chooseCollectEvidence(player, cost, source, c, ability); + CardCollectionView chosen = ComputerUtil.chooseCollectEvidence(player, cost, source, c, ability, isEffect()); return null == chosen ? null : PaymentDecision.card(chosen); } @@ -170,7 +170,7 @@ public class AiCostDecision extends CostDecisionMakerBase { // TODO Determine exile from same zone for AI return null; } else { - CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost, source, c, ability); + CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost, source, c, ability, isEffect()); return null == chosen ? null : PaymentDecision.card(chosen); } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 13f83ee7cda..11320a4f637 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -665,8 +665,8 @@ public class ComputerUtil { return sacList; } - public static CardCollection chooseCollectEvidence(final Player ai, CostCollectEvidence cost, final Card activate, int amount, SpellAbility sa) { - CardCollection typeList = new CardCollection(ai.getCardsIn(ZoneType.Graveyard)); + public static CardCollection chooseCollectEvidence(final Player ai, CostCollectEvidence cost, final Card activate, int amount, SpellAbility sa, final boolean effect) { + CardCollection typeList = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.canExiledBy(sa, effect)); if (CardLists.getTotalCMC(typeList) < amount) return null; @@ -692,7 +692,7 @@ public class ComputerUtil { return exileList; } - public static CardCollection chooseExileFrom(final Player ai, CostExile cost, final Card activate, final int amount, SpellAbility sa) { + public static CardCollection chooseExileFrom(final Player ai, CostExile cost, final Card activate, final int amount, SpellAbility sa, final boolean effect) { CardCollection typeList; if (cost.zoneRestriction != 1) { typeList = new CardCollection(ai.getGame().getCardsIn(cost.from)); @@ -700,6 +700,7 @@ public class ComputerUtil { typeList = new CardCollection(ai.getCardsIn(cost.from)); } typeList = CardLists.getValidCards(typeList, cost.getType().split(";"), activate.getController(), activate, sa); + typeList = CardLists.filter(typeList, CardPredicates.canExiledBy(sa, effect)); // don't exile the card we're pumping typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai); diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 5dbe7c483de..80a728afa40 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -62,7 +62,7 @@ public class ChangeZoneAi extends SpellAbilityAi { } int amt = part.getAbilityAmount(sa); needed += amt; - CardCollection toAdd = ComputerUtil.chooseExileFrom(ai, (CostExile) part, source, amt, sa); + CardCollection toAdd = ComputerUtil.chooseExileFrom(ai, (CostExile) part, source, amt, sa, true); if (toAdd != null) { payingCards.addAll(toAdd); } diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 7574e7716e8..4f24894ec25 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -24,7 +24,6 @@ import com.google.common.eventbus.EventBus; import forge.GameCommand; import forge.card.CardRarity; import forge.card.CardStateName; -import forge.card.CardType.Supertype; import forge.game.ability.AbilityKey; import forge.game.card.*; import forge.game.combat.Combat; @@ -950,36 +949,6 @@ public class Game { activePlanes = activePlane0; } - public void archenemy904_10() { - //904.10. If a non-ongoing scheme card is face up in the - //command zone, and it isn't the source of a triggered ability - //that has triggered but not yet left the stack, that scheme card - //is turned face down and put on the bottom of its owner's scheme - //deck the next time a player would receive priority. - //(This is a state-based action. See rule 704.) - - for (int i = 0; i < getCardsIn(ZoneType.Command).size(); i++) { - Card c = getCardsIn(ZoneType.Command).get(i); - if (c.isScheme() && !c.getType().hasSupertype(Supertype.Ongoing)) { - boolean foundonstack = false; - for (SpellAbilityStackInstance si : stack) { - if (si.getSourceCard().equals(c)) { - foundonstack = true; - break; - } - } - if (!foundonstack) { - getTriggerHandler().suppressMode(TriggerType.ChangesZone); - c.getController().getZone(ZoneType.Command).remove(c); - i--; - getTriggerHandler().clearSuppression(TriggerType.ChangesZone); - - c.getController().getZone(ZoneType.SchemeDeck).add(c); - } - } - } - } - public GameStage getAge() { return age; } diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 17ff2db6d2e..baebeccab4e 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -24,6 +24,7 @@ import forge.GameCommand; import forge.StaticData; import forge.card.CardStateName; import forge.card.MagicColor; +import forge.card.CardType.Supertype; import forge.deck.DeckSection; import forge.game.ability.*; import forge.game.card.*; @@ -725,7 +726,7 @@ public class GameAction { private void storeChangesZoneAll(Card c, Zone zoneFrom, Zone zoneTo, Map params) { if (params != null && params.containsKey(AbilityKey.InternalTriggerTable)) { - ((CardZoneTable) params.get(AbilityKey.InternalTriggerTable)).put(zoneFrom != null ? zoneFrom.getZoneType() : null, zoneTo.getZoneType(), c); + ((CardZoneTable) params.get(AbilityKey.InternalTriggerTable)).put(zoneFrom != null ? zoneFrom.getZoneType() : null, zoneTo.getZoneType(), c); } } @@ -747,16 +748,11 @@ public class GameAction { // use FThreads.invokeInNewThread to run code in a pooled thread return moveTo(zoneTo, c, null, cause, params); } - public final Card moveTo(final Zone zoneTo, Card c, Integer position, SpellAbility cause) { - return moveTo(zoneTo, c, position, cause, AbilityKey.newMap()); - } public final Card moveTo(final ZoneType name, final Card c, SpellAbility cause, Map params) { return moveTo(name, c, 0, cause, params); } - public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause) { - return moveTo(name, c, libPosition, cause, AbilityKey.newMap()); - } + public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map params) { // Call specific functions to set PlayerZone, then move onto moveTo switch(name) { @@ -764,7 +760,11 @@ public class GameAction { case Library: return moveToLibrary(c, libPosition, cause, params); case Battlefield: return moveToPlay(c, c.getController(), cause, params); case Graveyard: return moveToGraveyard(c, cause, params); - case Exile: return exile(c, cause, params); + case Exile: + if (!c.canExiledBy(cause, true)) { + return null; + } + return exile(c, cause, params); case Stack: return moveToStack(c, cause, params); case PlanarDeck: return moveToVariantDeck(c, ZoneType.PlanarDeck, libPosition, cause, params); case SchemeDeck: return moveToVariantDeck(c, ZoneType.SchemeDeck, libPosition, cause, params); @@ -943,6 +943,20 @@ public class GameAction { return copied; } + public final Card exileEffect(final Card effect) { + return exile(effect, null, null); + } + + public final void moveToCommand(final Card effect, final SpellAbility sa) { + moveToCommand(effect, sa, AbilityKey.newMap()); + } + public final void moveToCommand(final Card effect, final SpellAbility sa, Map params) { + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); + moveTo(ZoneType.Command, effect, sa, params); + effect.updateStateForView(); + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + } + public void ceaseToExist(Card c, boolean skipTrig) { if (c.isInZone(ZoneType.Stack)) { c.getGame().getStack().remove(c); @@ -1226,12 +1240,6 @@ public class GameAction { return false; } - // Max: I don't know where to put this! - but since it's a state based action, it must be in check state effects - if (game.getRules().hasAppliedVariant(GameType.Archenemy) - || game.getRules().hasAppliedVariant(GameType.ArchenemyRumble)) { - game.archenemy904_10(); - } - final boolean refreeze = game.getStack().isFrozen(); game.getStack().setFrozen(true); game.getTracker().freeze(); //prevent views flickering during while updating for state-based effects @@ -1266,6 +1274,7 @@ public class GameAction { checkAgain |= stateBasedAction704_5d(c); // Dungeon Card won't affect other cards, so don't need to set checkAgain stateBasedAction_Dungeon(c); + stateBasedAction_Scheme(c); } } } @@ -1545,6 +1554,15 @@ public class GameAction { } } + private void stateBasedAction_Scheme(Card c) { + if (!c.isScheme() || c.getType().hasSupertype(Supertype.Ongoing)) { + return; + } + if (!game.getStack().hasSourceOnStack(c, null)) { + moveTo(ZoneType.SchemeDeck, c, null, AbilityKey.newMap()); + } + } + private boolean stateBasedAction704_attach(Card c, CardCollection unAttachList) { boolean checkAgain = false; @@ -1967,7 +1985,7 @@ public class GameAction { } reveal(cards, game.getZoneOf(firstCard).getZoneType(), cardOwner, dontRevealToOwner, messagePrefix, msgAddSuffix); } - + public void reveal(CardCollectionView cards, ZoneType zt, Player cardOwner, boolean dontRevealToOwner, String messagePrefix) { reveal(cards, zt, cardOwner, dontRevealToOwner, messagePrefix, true); } diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 1f3e35345d8..331c951db16 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -48,7 +48,6 @@ import forge.game.replacement.ReplacementLayer; import forge.game.spellability.*; import forge.game.staticability.StaticAbilityLayer; import forge.game.trigger.Trigger; -import forge.game.trigger.TriggerType; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.Lang; @@ -757,10 +756,7 @@ public final class GameActionUtil { eff.updateStateForView(); - // TODO: Add targeting to the effect so it knows who it's dealing with - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, null, null); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); return eff; } 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 19299c95342..c906d0a7b74 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -536,11 +536,7 @@ public abstract class SpellAbilityEffect { eff.copyChangedTextFrom(card); } - // TODO: Add targeting to the effect so it knows who it's dealing with - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, null); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); } protected static void addLeaveBattlefieldReplacement(final Card eff, final String zone) { @@ -648,22 +644,9 @@ public abstract class SpellAbilityEffect { eff.copyChangedTextFrom(host); } - final GameCommand endEffect = new GameCommand() { - private static final long serialVersionUID = -5861759814760561373L; + game.getEndOfTurn().addUntil(exileEffectCommand(game, eff)); - @Override - public void run() { - game.getAction().exile(eff, null, null); - } - }; - - game.getEndOfTurn().addUntil(endEffect); - - // TODO: Add targeting to the effect so it knows who it's dealing with - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, null); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); } } @@ -991,4 +974,15 @@ public abstract class SpellAbilityEffect { } return new CardZoneTable(lastStateBattlefield, lastStateGraveyard); } + + public static GameCommand exileEffectCommand(final Game game, final Card effect) { + return new GameCommand() { + private static final long serialVersionUID = 1L; + + @Override + public void run() { + game.getAction().exileEffect(effect); + } + }; + } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java index 78f9ffe3843..c45614659d7 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AddTurnEffect.java @@ -2,7 +2,6 @@ package forge.game.ability.effects; import forge.game.Game; import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -11,8 +10,6 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; import forge.util.Lang; import forge.util.Localizer; @@ -79,10 +76,7 @@ public class AddTurnEffect extends SpellAbilityEffect { eff.addStaticAbility(stEffect); - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap()); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index cc2c02d686e..28e04c615cf 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -197,6 +197,9 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { c.setController(sa.getActivatingPlayer(), game.getNextTimestamp()); movedCard = game.getAction().moveToPlay(c, sa.getActivatingPlayer(), sa, moveParams); } else { + if (destination == ZoneType.Exile && !c.canExiledBy(sa, true)) { + continue; + } movedCard = game.getAction().moveTo(destination, c, libraryPos, sa, moveParams); if (destination == ZoneType.Exile) { handleExiledWith(movedCard, sa); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index ec4765e08b4..69f55e61a80 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -702,6 +702,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } else { // might set before card is moved only for nontoken if (destination.equals(ZoneType.Exile)) { + if (!gameCard.canExiledBy(sa, true)) { + continue; + } handleExiledWith(gameCard, sa); } @@ -1384,6 +1387,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } } else if (destination.equals(ZoneType.Exile)) { + if (!c.canExiledBy(sa, true)) { + continue; + } movedCard = game.getAction().exile(c, sa, moveParams); handleExiledWith(movedCard, sa); @@ -1553,6 +1559,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } else if (srcSA.getParam("Destination").equals("Graveyard")) { movedCard = game.getAction().moveToGraveyard(tgtHost, srcSA, params); } else if (srcSA.getParam("Destination").equals("Exile")) { + if (!tgtHost.canExiledBy(srcSA, true)) { + return; + } movedCard = game.getAction().exile(tgtHost, srcSA, params); handleExiledWith(movedCard, srcSA); } else if (srcSA.getParam("Destination").equals("TopOfLibrary")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java index 0fa60ec6c61..d99abf90b97 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java @@ -265,6 +265,9 @@ public class CounterEffect extends SpellAbilityEffect { } else if (destination.equals("Graveyard")) { movedCard = game.getAction().moveToGraveyard(c, srcSA, params); } else if (destination.equals("Exile")) { + if (!c.canExiledBy(srcSA, true)) { + return false; + } movedCard = game.getAction().exile(c, srcSA, params); } else if (destination.equals("Hand")) { movedCard = game.getAction().moveToHand(c, srcSA, params); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffectBase.java index 5164c297441..3200183dca6 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffectBase.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffectBase.java @@ -6,7 +6,6 @@ import forge.GameCommand; import forge.game.Game; import forge.game.GameEntity; import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -16,8 +15,6 @@ import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; import forge.util.TextUtil; public abstract class DamagePreventEffectBase extends SpellAbilityEffect { @@ -66,10 +63,7 @@ public abstract class DamagePreventEffectBase extends SpellAbilityEffect { addForgetOnMovedTrigger(eff, "Battlefield"); } - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap()); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); o.getView().updatePreventNextDamage(o); if (o instanceof Player) { @@ -81,7 +75,7 @@ public abstract class DamagePreventEffectBase extends SpellAbilityEffect { @Override public void run() { - game.getAction().exile(eff, null, null); + game.getAction().exileEffect(eff); o.getView().updatePreventNextDamage(o); if (o instanceof Player) { game.fireEvent(new GameEventPlayerStatsChanged((Player) o, false)); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index 573f8f1600b..03b982c0317 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -379,8 +379,12 @@ public class DigEffect extends SpellAbilityEffect { for (Card c : movedCards) { if (destZone1.equals(ZoneType.Library) || destZone1.equals(ZoneType.PlanarDeck) || destZone1.equals(ZoneType.SchemeDeck)) { - c = game.getAction().moveTo(destZone1, c, libraryPosition, sa); + c = game.getAction().moveTo(destZone1, c, libraryPosition, sa, AbilityKey.newMap()); } else { + if (destZone1.equals(ZoneType.Exile) && !c.canExiledBy(sa, true)) { + continue; + } + if (sa.hasParam("Tapped")) { c.setTapped(true); } @@ -470,6 +474,9 @@ public class DigEffect extends SpellAbilityEffect { } else { // just move them randomly for (Card c : rest) { + if (destZone2 == ZoneType.Exile && !c.canExiledBy(sa, true)) { + continue; + } c = game.getAction().moveTo(destZone2, c, sa, moveParams); if (destZone2 == ZoneType.Exile) { if (sa.hasParam("ExileWithCounter")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java index 2a5d8748074..392e47f221b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java @@ -6,6 +6,7 @@ import java.util.Map; import com.google.common.collect.Maps; import forge.game.Game; +import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -120,7 +121,7 @@ public class DigMultipleEffect extends SpellAbilityEffect { if (!sa.hasParam("ChangeLater")) { if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) { - c = game.getAction().moveTo(destZone1, c, libraryPosition, sa); + c = game.getAction().moveTo(destZone1, c, libraryPosition, sa, AbilityKey.newMap()); } else { if (destZone1.equals(ZoneType.Battlefield)) { if (sa.hasParam("Tapped")) { @@ -164,7 +165,7 @@ public class DigMultipleEffect extends SpellAbilityEffect { } for (final Card c : afterOrder) { final ZoneType origin = c.getZone().getZoneType(); - Card m = game.getAction().moveTo(destZone2, c, libraryPosition2, sa); + Card m = game.getAction().moveTo(destZone2, c, libraryPosition2, sa, AbilityKey.newMap()); if (m != null && !origin.equals(m.getZone().getZoneType())) { table.put(origin, m.getZone().getZoneType(), m); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java index b7e90892268..77175b86dee 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java @@ -51,7 +51,6 @@ import java.util.*; CardCollection drafted = new CardCollection(); for (int i = 0; i < numToDraft; i++) { - String chosen = ""; Collections.shuffle(spellbook); List draftOptions = new ArrayList<>(); for (String name : spellbook.subList(0, 3)) { @@ -69,6 +68,10 @@ import java.util.*; final CardZoneTable triggerList = new CardZoneTable(); for (final Card c : drafted) { + if (zone.equals(ZoneType.Exile) && !c.canExiledBy(sa, true)) { + continue; + } + Card made = game.getAction().moveTo(zone, c, sa, moveParams); if (zone.equals(ZoneType.Exile)) { handleExiledWith(made, sa); 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 e32fe03e4dc..c383098665d 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 @@ -8,7 +8,6 @@ import java.util.Map; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import forge.GameCommand; import forge.ImageKeys; import forge.card.CardRarity; import forge.game.Game; @@ -26,7 +25,6 @@ import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerHandler; -import forge.game.trigger.TriggerType; import forge.game.zone.ZoneType; import forge.util.TextUtil; import forge.util.collect.FCollection; @@ -307,30 +305,14 @@ public class EffectEffect extends SpellAbilityEffect { } if (duration == null || !duration.equals("Permanent")) { - final GameCommand endEffect = new GameCommand() { - private static final long serialVersionUID = -5861759814760561373L; - - @Override - public void run() { - game.getAction().exile(eff, null, null); - } - }; - - addUntilCommand(sa, endEffect, controller); + addUntilCommand(sa, exileEffectCommand(game, eff), controller); } if (sa.hasParam("ImprintOnHost")) { hostCard.addImprintedCard(eff); } - // TODO: Add targeting to the effect so it knows who it's dealing with - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, params); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); - //if (effectTriggers != null) { - // game.getTriggerHandler().registerActiveTrigger(cmdEffect, false); - //} + game.getAction().moveToCommand(eff, sa); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/FogEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FogEffect.java index d782a937e06..563709d401e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FogEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FogEffect.java @@ -1,15 +1,11 @@ package forge.game.ability.effects; -import forge.GameCommand; import forge.game.Game; -import forge.game.ability.AbilityKey; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.spellability.SpellAbility; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; public class FogEffect extends SpellAbilityEffect { @@ -32,18 +28,8 @@ public class FogEffect extends SpellAbilityEffect { ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true); eff.addReplacementEffect(re); - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap()); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); - game.getEndOfTurn().addUntil(new GameCommand() { - private static final long serialVersionUID = -3297629217432253089L; - - @Override - public void run() { - game.getAction().exile(eff, null, null); - } - }); + game.getEndOfTurn().addUntil(exileEffectCommand(game, eff)); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/MeldEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MeldEffect.java index ab6372ca33e..965c6d4dafd 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MeldEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MeldEffect.java @@ -39,7 +39,7 @@ public class MeldEffect extends SpellAbilityEffect { Card secondary = controller.getController().chooseSingleEntityForEffect(field, sa, Localizer.getInstance().getMessage("lblChooseCardToMeld"), null); - CardCollection exiled = new CardCollection(Arrays.asList(hostCard, secondary)); + CardCollection exiled = CardLists.filter(Arrays.asList(hostCard, secondary), CardPredicates.canExiledBy(sa, true)); Map moveParams = AbilityKey.newMap(); CardZoneTable table = new CardZoneTable(sa.getLastStateBattlefield(), sa.getLastStateGraveyard()); @@ -48,8 +48,12 @@ public class MeldEffect extends SpellAbilityEffect { exiled = game.getAction().exile(exiled, sa, moveParams); table.triggerChangesZoneAll(game, sa); - Card primary = exiled.get(0); - secondary = exiled.get(1); + if (exiled.size() < 2) { + return; + } + + Card primary = exiled.get(hostCard); + secondary = exiled.get(secondary); // cards has wrong name in exile if (!primary.sharesNameWith(primName) || !secondary.sharesNameWith(secName)) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java index 8ac9c56e1dd..53a6c4a73d0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java @@ -80,7 +80,7 @@ public class MutateEffect extends SpellAbilityEffect { game.getTriggerHandler().clearActiveTriggers(target, null); game.getTriggerHandler().registerActiveTrigger(target, false); - game.getAction().moveTo(p.getZone(ZoneType.Merged), host, sa); + game.getAction().moveTo(p.getZone(ZoneType.Merged), host, sa, AbilityKey.newMap()); host.setTapped(target.isTapped()); host.setFlipped(target.isFlipped()); diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 16bb2027152..ea69036f4eb 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -15,7 +15,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import forge.GameCommand; import forge.StaticData; import forge.card.CardRulesPredicates; import forge.game.Game; @@ -42,7 +41,6 @@ import forge.game.spellability.AlternativeCost; import forge.game.spellability.LandAbility; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityPredicates; -import forge.game.trigger.TriggerType; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.item.PaperCard; @@ -521,24 +519,11 @@ public class PlayEffect extends SpellAbilityEffect { eff.copyChangedTextFrom(hostCard); } - final GameCommand endEffect = new GameCommand() { - private static final long serialVersionUID = -5861759814760561373L; - - @Override - public void run() { - game.getAction().exile(eff, null, null); - } - }; - - game.getEndOfTurn().addUntil(endEffect); + game.getEndOfTurn().addUntil(exileEffectCommand(game, eff)); tgtSA.addRollbackEffect(eff); - // TODO: Add targeting to the effect so it knows who it's dealing with - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, moveParams); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); } protected void addIllusionaryMaskReplace(Card c, SpellAbility sa, Map moveParams) { @@ -572,9 +557,6 @@ public class PlayEffect extends SpellAbilityEffect { addExileOnMovedTrigger(eff, "Battlefield"); addExileOnCounteredTrigger(eff); - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, moveParams); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/RegenerateBaseEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RegenerateBaseEffect.java index f93223d37d1..0ee64defd92 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RegenerateBaseEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RegenerateBaseEffect.java @@ -2,10 +2,8 @@ package forge.game.ability.effects; import java.util.Collection; -import forge.GameCommand; import forge.game.Game; import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -13,8 +11,6 @@ import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; public abstract class RegenerateBaseEffect extends SpellAbilityEffect { @@ -67,19 +63,8 @@ public abstract class RegenerateBaseEffect extends SpellAbilityEffect { for (final Card c : list) { c.incShieldCount(); } - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap()); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); - final GameCommand untilEOT = new GameCommand() { - private static final long serialVersionUID = 259368227093961103L; - - @Override - public void run() { - game.getAction().exile(eff, null, null); - } - }; - game.getEndOfTurn().addUntil(untilEOT); + game.getEndOfTurn().addUntil(exileEffectCommand(game, eff)); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java index 5f741ed00ac..4e82ae57c00 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java @@ -48,7 +48,7 @@ public class ReplaceDamageEffect extends SpellAbilityEffect { if (!StringUtils.isNumeric(varValue) && card.getSVar(varValue).startsWith("Number$")) { if (card.isImmutable() && prevent <= 0) { - game.getAction().exile(card, null, null); + game.getAction().exileEffect(card); } else { card.setSVar(varValue, "Number$" + prevent); card.updateAbilityTextForView(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java index 39186ec7f59..86647c80f24 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java @@ -46,7 +46,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect { prevent -= n; if (card.isImmutable() && prevent <= 0) { - game.getAction().exile(card, null, null); + game.getAction().exileEffect(card); } else if (!StringUtils.isNumeric(varValue)) { sa.setSVar(varValue, "Number$" + prevent); card.updateAbilityTextForView(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/SetInMotionEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SetInMotionEffect.java index f471abd7c01..bc94921a9cb 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SetInMotionEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SetInMotionEffect.java @@ -12,7 +12,6 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.staticability.StaticAbilityCantSetSchemesInMotion; import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; public class SetInMotionEffect extends SpellAbilityEffect { @@ -40,16 +39,14 @@ public class SetInMotionEffect extends SpellAbilityEffect { return; } - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, controller.getActiveScheme(), null, AbilityKey.newMap()); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(controller.getActiveScheme(), sa); // Run triggers final Map runParams = AbilityKey.newMap(); runParams.put(AbilityKey.Scheme, controller.getActiveScheme()); game.getTriggerHandler().runTrigger(TriggerType.SetInMotion, runParams, false); } else { - controller.setSchemeInMotion(); + controller.setSchemeInMotion(sa); } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/SkipPhaseEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SkipPhaseEffect.java index 74273993a01..bdb5a5b4c3a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SkipPhaseEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SkipPhaseEffect.java @@ -5,7 +5,6 @@ import java.util.List; import forge.GameCommand; import forge.game.Game; import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityKey; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.player.Player; @@ -13,8 +12,6 @@ import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementLayer; import forge.game.spellability.SpellAbility; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; public class SkipPhaseEffect extends SpellAbilityEffect { @@ -102,16 +99,7 @@ public class SkipPhaseEffect extends SpellAbilityEffect { re.setOverridingAbility(exile); } if (duration != null) { - final GameCommand endEffect = new GameCommand() { - private static final long serialVersionUID = -5861759814760561373L; - - @Override - public void run() { - game.getAction().exile(eff, null, null); - } - }; - - addUntilCommand(sa, endEffect); + addUntilCommand(sa, exileEffectCommand(game, eff)); } eff.addReplacementEffect(re); @@ -121,18 +109,12 @@ public class SkipPhaseEffect extends SpellAbilityEffect { @Override public void run() { - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap()); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); } }; game.getUpkeep().addUntil(player, startEffect); } else { - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap()); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java index a4adb77a8fe..31ebed658ea 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SkipTurnEffect.java @@ -4,7 +4,6 @@ import java.util.List; import forge.game.Game; import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; @@ -14,8 +13,6 @@ import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementLayer; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; -import forge.game.trigger.TriggerType; -import forge.game.zone.ZoneType; import forge.util.Lang; public class SkipTurnEffect extends SpellAbilityEffect { @@ -61,10 +58,7 @@ public class SkipTurnEffect extends SpellAbilityEffect { re.setOverridingAbility(calcTurn); eff.addReplacementEffect(re); - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, sa, AbilityKey.newMap()); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, sa); } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java b/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java index 9fcd4af1c63..287799edd87 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java @@ -59,9 +59,7 @@ public class VentureEffect extends SpellAbilityEffect { String message = Localizer.getInstance().getMessage("lblChooseDungeon"); Card dungeon = player.getController().chooseDungeon(player, dungeonCards, message); - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, dungeon, sa, moveParams); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(dungeon, sa, moveParams); return dungeon; } 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 eea05e60381..c929692e504 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -7058,6 +7058,18 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return !StaticAbilityCantSacrifice.cantSacrifice(this, source, effect); } + public final boolean canExiledBy(final SpellAbility source, final boolean effect) { + final Card gameCard = game.getCardState(this, null); + // gameCard is LKI in that case, the card is not in game anymore + // or the timestamp did change + // this should check Self too + if (gameCard == null || !this.equalsWithTimestamp(gameCard)) { + return false; + } + + return !StaticAbilityCantExile.cantExile(this, source, effect); + } + public CardRules getRules() { return cardRules; } diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java index 78f8e99a461..d97dc633ed7 100644 --- a/forge-game/src/main/java/forge/game/card/CardPredicates.java +++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java @@ -251,6 +251,15 @@ public final class CardPredicates { }; } + public static final Predicate canExiledBy(final SpellAbility sa, final boolean effect) { + return new Predicate() { + @Override + public boolean apply(final Card c) { + return c.canExiledBy(sa, effect); + } + }; + } + public static final Predicate canBeAttached(final Card aura, final SpellAbility sa) { return new Predicate() { @Override diff --git a/forge-game/src/main/java/forge/game/cost/CostCollectEvidence.java b/forge-game/src/main/java/forge/game/cost/CostCollectEvidence.java index 9327fe0d4ce..96ccc36853d 100644 --- a/forge-game/src/main/java/forge/game/cost/CostCollectEvidence.java +++ b/forge-game/src/main/java/forge/game/cost/CostCollectEvidence.java @@ -7,6 +7,7 @@ import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; import forge.game.card.CardLists; +import forge.game.card.CardPredicates; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; @@ -47,7 +48,7 @@ public class CostCollectEvidence extends CostPartWithList { // This may need to be updated if we get a card like "Cards in graveyards can't be exiled to pay for costs" - return CardLists.getTotalCMC(payer.getCardsIn(ZoneType.Graveyard)) >= amount; + return CardLists.getTotalCMC(CardLists.filter(payer.getCardsIn(ZoneType.Graveyard), CardPredicates.canExiledBy(ability, effect))) >= amount; } @Override diff --git a/forge-game/src/main/java/forge/game/cost/CostExile.java b/forge-game/src/main/java/forge/game/cost/CostExile.java index 935cd244be4..fdabe26e597 100644 --- a/forge-game/src/main/java/forge/game/cost/CostExile.java +++ b/forge-game/src/main/java/forge/game/cost/CostExile.java @@ -175,8 +175,8 @@ public class CostExile extends CostPartWithList { type = TextUtil.fastReplace(type, "FromTopGrave", ""); } - CardCollection list = new CardCollection(zoneRestriction != 1 ? game.getCardsIn(this.from) : - payer.getCardsIn(this.from)); + CardCollection list = CardLists.filter(zoneRestriction != 1 ? game.getCardsIn(this.from) : + payer.getCardsIn(this.from), CardPredicates.canExiledBy(ability, effect)); if (this.payCostFromSource()) { return list.contains(source); @@ -216,7 +216,6 @@ public class CostExile extends CostPartWithList { } if (totalCMC) { - int needed = Integer.parseInt(this.getAmount().split("\\+")[0]); if (totalM.equals("X") && ability.getXManaCostPaid() == null) { // X hasn't yet been decided, let it pass return true; } 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 c77a31f2a0e..cd12c1cffe6 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -281,7 +281,7 @@ public class PhaseHandler implements java.io.Serializable { case MAIN1: { if (playerTurn.isArchenemy()) { - playerTurn.setSchemeInMotion(); + playerTurn.setSchemeInMotion(null); } if (playerTurn.hasRadiationEffect()) { handleRadiation(); 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 9e7d9850518..a0db5462c18 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -266,7 +266,7 @@ public class Player extends GameEntity implements Comparable { return activeScheme; } - public void setSchemeInMotion() { + public void setSchemeInMotion(SpellAbility cause) { if (StaticAbilityCantSetSchemesInMotion.any(getGame())) { return; } @@ -280,10 +280,9 @@ public class Player extends GameEntity implements Comparable { moveParams.put(AbilityKey.LastStateBattlefield, game.getLastStateBattlefield()); moveParams.put(AbilityKey.LastStateGraveyard, game.getLastStateGraveyard()); - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); activeScheme = getZone(ZoneType.SchemeDeck).get(0); - game.getAction().moveTo(ZoneType.Command, activeScheme, null, moveParams); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(activeScheme, cause); + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); // Run triggers final Map runParams = AbilityKey.newMap(); @@ -2690,7 +2689,7 @@ public class Player extends GameEntity implements Comparable { game.getView().updatePlanarPlayer(getView()); for (Card c : destinations) { - currentPlanes.add(game.getAction().moveTo(getZone(ZoneType.Command), c, sa)); + currentPlanes.add(game.getAction().moveTo(getZone(ZoneType.Command), c, sa, AbilityKey.newMap())); planeswalkedToThisTurn.add(c); } @@ -2712,7 +2711,7 @@ public class Player extends GameEntity implements Comparable { for (final Card plane : currentPlanes) { plane.clearControllers(); - game.getAction().moveTo(ZoneType.PlanarDeck, plane, -1, null); + game.getAction().moveTo(ZoneType.PlanarDeck, plane, -1, null, AbilityKey.newMap()); } currentPlanes.clear(); } @@ -3801,7 +3800,7 @@ public class Player extends GameEntity implements Comparable { } if (c.isInZone(ZoneType.Sideboard)) { // Sideboard Lesson to Hand game.getAction().reveal(new CardCollection(c), c.getOwner(), true); - Card moved = game.getAction().moveTo(ZoneType.Hand, c, sa, params); + game.getAction().moveTo(ZoneType.Hand, c, sa, params); } else if (c.isInZone(ZoneType.Hand)) { // Discard and Draw boolean firstDiscard = getNumDiscardedThisTurn() == 0; if (discard(c, sa, true, params) != null) { diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index 30261d2ba81..e405fa9ecc0 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -250,10 +250,7 @@ public class AbilityManaPart implements java.io.Serializable { SpellAbilityEffect.addForgetOnMovedTrigger(eff, "Stack"); - game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - game.getAction().moveTo(ZoneType.Command, eff, null, null); - eff.updateStateForView(); - game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + game.getAction().moveToCommand(eff, null); } /** diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantExile.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantExile.java new file mode 100644 index 00000000000..240b7b30509 --- /dev/null +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantExile.java @@ -0,0 +1,42 @@ +package forge.game.staticability; + +import forge.game.Game; +import forge.game.card.Card; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +public class StaticAbilityCantExile { + + static String MODE = "CantExile"; + + public static boolean cantExile(final Card card, final SpellAbility cause, final boolean effect) { + final Game game = card.getGame(); + for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (!stAb.checkConditions(MODE)) { + continue; + } + + if (applyCantExileAbility(stAb, card, cause, effect)) { + return true; + } + } + } + return false; + } + + public static boolean applyCantExileAbility(final StaticAbility stAb, final Card card, final SpellAbility cause, final boolean effect) { + if (!stAb.matchesValidParam("ValidCard", card)) { + return false; + } + if (stAb.hasParam("ForCost")) { + if ("True".equalsIgnoreCase(stAb.getParam("ForCost")) == effect) { + return false; + } + } + if (!stAb.matchesValidParam("ValidCause", cause)) { + return false; + } + return true; + } +} diff --git a/forge-gui/res/cardsfolder/upcoming/the_master_multiplied.txt b/forge-gui/res/cardsfolder/upcoming/the_master_multiplied.txt new file mode 100644 index 00000000000..0575dbcc0e5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_master_multiplied.txt @@ -0,0 +1,9 @@ +Name:The Master, Multiplied +ManaCost:4 B R +Types:Legendary Creature Time Lord Rogue +PT:4/3 +K:Myriad +S:Mode$ IgnoreLegendRule | ValidCard$ Creature.YouCtrl+token | Description$ The "legend rule" doesn't apply to creature tokens you control. +S:Mode$ CantSacrifice | ValidCard$ Creature.YouCtrl+token | ValidCause$ Triggered.YouCtrl | ForCost$ False | Description$ Triggered abilities you control can't cause you to sacrifice or exile creature tokens you control. +S:Mode$ CantExile | ValidCard$ Creature.YouCtrl+token | ValidCause$ Triggered.YouCtrl | ForCost$ False | Secondary$ True | Description$ Triggered abilities you control can't cause you to sacrifice or exile creature tokens you control. +Oracle:Myriad\nThe "legend rule" doesn't apply to creature tokens you control.\nTriggered abilities you control can't cause you to sacrifice or exile creature tokens you control. diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 790e48d036a..95fd7a3e754 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -81,7 +81,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostCollectEvidence cost) { - CardCollection list = new CardCollection(player.getCardsIn(ZoneType.Graveyard)); + CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Graveyard), CardPredicates.canExiledBy(ability, isEffect())); final int total = AbilityUtils.calculateAmount(source, cost.getAmount(), ability); final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 0, list.size(), list, ability, total); @@ -239,6 +239,9 @@ public class HumanCostDecision extends CostDecisionMakerBase { @Override public PaymentDecision visit(final CostExile cost) { if (cost.payCostFromSource()) { + if (!source.canExiledBy(ability, isEffect())) { + return null; + } return source.getZone() == player.getZone(cost.from.get(0)) && confirmAction(cost, Localizer.getInstance().getMessage("lblExileConfirm", CardTranslation.getTranslatedName(source.getName()))) ? PaymentDecision.card(source) : null; } @@ -274,6 +277,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { return PaymentDecision.card(list); } list = CardLists.getValidCards(list, type.split(";"), player, source, ability); + list = CardLists.filter(list, CardPredicates.canExiledBy(ability, isEffect())); if (totalCMC) { int needed = Integer.parseInt(cost.getAmount().split("\\+")[0]);