diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 924cdcd9d80..1f4e66b3198 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -224,15 +224,13 @@ public class PlayerControllerAi extends PlayerController { } @Override - public boolean confirmTrigger(WrappedAbility wrapper, Map triggerParams, boolean isMandatory) { + public boolean confirmTrigger(WrappedAbility wrapper) { final SpellAbility sa = wrapper.getWrappedAbility(); //final Trigger regtrig = wrapper.getTrigger(); if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Deathmist Raptor")) { return true; } - if (triggerParams.containsKey("DelayedTrigger") || isMandatory) { - //TODO: The only card with an optional delayed trigger is Shirei, Shizo's Caretaker, - // needs to be expanded when a more difficult cards comes up + if (wrapper.isMandatory()) { return true; } // Store/replace target choices more properly to get this SA cleared. diff --git a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java index 33cd4a6aaf7..0a95d27b9e2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java @@ -46,10 +46,6 @@ public class DelayedTriggerEffect extends SpellAbilityEffect { final Trigger delTrig = TriggerHandler.parseTrigger(mapParams, sa.getHostCard(), true); - if (sa.hasParam("CopyTriggeringObjects")) { - delTrig.setStoredTriggeredObjects(sa.getTriggeringObjects()); - } - if (triggerRemembered != null) { for (final String rem : triggerRemembered.split(",")) { for (final Object o : AbilityUtils.getDefinedObjects(sa.getHostCard(), rem, sa)) { @@ -78,6 +74,11 @@ public class DelayedTriggerEffect extends SpellAbilityEffect { if (ApiType.SetState == overridingSA.getApi()) { overridingSA.setSVar("StoredTransform", String.valueOf(sa.getHostCard().getTransformedTimestamp())); } + + if (sa.hasParam("CopyTriggeringObjects")) { + overridingSA.setTriggeringObjects(sa.getTriggeringObjects()); + } + delTrig.setOverridingAbility(overridingSA); } final TriggerHandler trigHandler = sa.getActivatingPlayer().getGame().getTriggerHandler(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java index 7b0e66c1c42..c9b84d59cfa 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java @@ -47,10 +47,6 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect { final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, sa.getHostCard(), sa.isIntrinsic()); - if (sa.hasParam("CopyTriggeringObjects")) { - immediateTrig.setStoredTriggeredObjects(sa.getTriggeringObjects()); - } - // Need to copy paid costs if (triggerRemembered != null) { @@ -73,6 +69,11 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect { if (mapParams.containsKey("Execute") || sa.hasAdditionalAbility("Execute")) { SpellAbility overridingSA = sa.getAdditionalAbility("Execute"); overridingSA.setActivatingPlayer(sa.getActivatingPlayer()); + + if (sa.hasParam("CopyTriggeringObjects")) { + overridingSA.setTriggeringObjects(sa.getTriggeringObjects()); + } + immediateTrig.setOverridingAbility(overridingSA); } final TriggerHandler trigHandler = sa.getActivatingPlayer().getGame().getTriggerHandler(); diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index 8f47c474362..dd9f68bd571 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -27,7 +27,6 @@ import forge.card.mana.ManaCost; import forge.game.CardTraitBase; import forge.game.Game; import forge.game.ability.AbilityFactory; -import forge.game.ability.AbilityUtils; import forge.game.cost.Cost; import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; @@ -194,8 +193,8 @@ public class CardFactory { } final SpellAbility copySA; - if (sa.isTrigger()) { - copySA = getCopiedTriggeredAbility(sa); + if (sa.isTrigger() && sa.isWrapper()) { + copySA = getCopiedTriggeredAbility((WrappedAbility)sa, c); } else { copySA = sa.copy(c, false); } @@ -214,10 +213,6 @@ public class CardFactory { if (!copySA.isTrigger()) { copySA.setPayCosts(new Cost("", sa.isAbility())); } - if (sa.getTargetRestrictions() != null) { - TargetRestrictions target = new TargetRestrictions(sa.getTargetRestrictions()); - copySA.setTargetRestrictions(target); - } copySA.setActivatingPlayer(controller); if (bCopyDetails) { @@ -587,49 +582,12 @@ public class CardFactory { * * return a wrapped ability */ - public static SpellAbility getCopiedTriggeredAbility(final SpellAbility sa) { + public static SpellAbility getCopiedTriggeredAbility(final WrappedAbility sa, final Card newHost) { if (!sa.isTrigger()) { return null; } - // Find trigger - Trigger t = null; - if (sa.isWrapper()) { - // copy trigger? - t = sa.getTrigger(); - } else { // some keyword ability, e.g. Exalted, Annihilator - return sa.copy(); - } - // set up copied wrapped ability - SpellAbility trig = t.getOverridingAbility(); - if (trig == null) { - trig = AbilityFactory.getAbility(sa.getHostCard().getSVar(t.getParam("Execute")), sa.getHostCard()); - } - trig.setHostCard(sa.getHostCard()); - trig.setTrigger(true); - trig.setSourceTrigger(t.getId()); - sa.setTriggeringObjects(sa.getTriggeringObjects()); - trig.setTriggerRemembered(t.getTriggerRemembered()); - if (t.getStoredTriggeredObjects() != null) { - trig.setTriggeringObjects(t.getStoredTriggeredObjects()); - } - trig.setActivatingPlayer(sa.getActivatingPlayer()); - if (t.hasParam("TriggerController")) { - Player p = AbilityUtils.getDefinedPlayers(t.getHostCard(), t.getParam("TriggerController"), trig).get(0); - trig.setActivatingPlayer(p); - } - - if (t.hasParam("RememberController")) { - sa.getHostCard().addRemembered(sa.getActivatingPlayer()); - } - - trig.setStackDescription(trig.toString()); - - WrappedAbility wrapperAbility = new WrappedAbility(t, trig, ((WrappedAbility) sa).getDecider()); - wrapperAbility.setTrigger(true); - wrapperAbility.setMandatory(sa.isMandatory()); - wrapperAbility.setDescription(wrapperAbility.getStackDescription()); - return wrapperAbility; + return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, false), sa.getDecider()); } public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase sa) { diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index 0ef21340298..8c8f7312410 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -122,7 +122,7 @@ public abstract class PlayerController { public abstract boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message); public abstract boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode bidlife, String string, int bid, Player winner); public abstract boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message); - public abstract boolean confirmTrigger(WrappedAbility sa, Map triggerParams, boolean isMandatory); + public abstract boolean confirmTrigger(WrappedAbility sa); public abstract Player chooseStartingPlayer(boolean isFirstGame); public abstract CardCollection orderBlockers(Card attacker, CardCollection blockers); diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 7ae20008dcd..4019e19d81b 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -998,7 +998,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean isMandatory() { - return false; + return isTrigger() && !isOptionalTrigger(); } public final boolean canTarget(final GameObject entity) { diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java index d6b065f923d..03b98fc793a 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -66,40 +66,12 @@ public abstract class Trigger extends TriggerReplacementBase { private TriggerType mode; - private Map storedTriggeredObjects = null; - private List triggerRemembered = Lists.newArrayList(); // number of times this trigger was activated this this turn // used to handle once-per-turn triggers like Crawling Sensation private int numberTurnActivations = 0; - /** - *

- * Setter for the field storedTriggeredObjects. - *

- * - * @param storedTriggeredObjects - * a {@link java.util.HashMap} object. - * @since 1.0.15 - */ - public final void setStoredTriggeredObjects(final Map storedTriggeredObjects) { - this.storedTriggeredObjects = AbilityKey.newMap(storedTriggeredObjects); - } - - /** - *

- * Getter for the field storedTriggeredObjects. - *

- * - * @return a {@link java.util.HashMap} object. - * @since 1.0.15 - */ - public final Map getStoredTriggeredObjects() { - return this.storedTriggeredObjects; - } - - private Set validPhases; /** diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 2fa84917bb5..f5ec0661a96 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -17,7 +17,6 @@ */ package forge.game.trigger; -import forge.card.mana.ManaCost; import forge.game.Game; import forge.game.GlobalRuleChange; import forge.game.ability.AbilityFactory; @@ -32,11 +31,9 @@ import forge.game.card.CardZoneTable; import forge.game.keyword.KeywordInterface; import forge.game.phase.PhaseType; import forge.game.player.Player; -import forge.game.spellability.Ability; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; -import forge.game.spellability.TargetRestrictions; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.FileSection; @@ -528,11 +525,7 @@ public class TriggerHandler { sa = regtrig.getOverridingAbility(); if (sa == null) { if (!regtrig.hasParam("Execute")) { - sa = new Ability(host, ManaCost.ZERO) { - @Override - public void resolve() { - } - }; + sa = new SpellAbility.EmptySa(host); } else { String name = regtrig.getParam("Execute"); @@ -563,9 +556,6 @@ public class TriggerHandler { sa.setSourceTrigger(regtrig.getId()); regtrig.setTriggeringObjects(sa, runParams); sa.setTriggerRemembered(regtrig.getTriggerRemembered()); - if (regtrig.getStoredTriggeredObjects() != null) { - sa.setTriggeringObjects(regtrig.getStoredTriggeredObjects()); - } if (sa.getDeltrigActivatingPlayer() != null) { // make sure that the original delayed trigger activator is restored @@ -591,33 +581,20 @@ public class TriggerHandler { } Player decider = null; - boolean mand = false; + boolean isMandatory = false; if (regtrig.hasParam("OptionalDecider")) { sa.setOptionalTrigger(true); decider = AbilityUtils.getDefinedPlayers(host, regtrig.getParam("OptionalDecider"), sa).get(0); } else if (sa instanceof AbilitySub || !sa.hasParam("Cost") || sa.getParam("Cost").equals("0")) { - mand = true; + isMandatory = true; } else { // triggers with a cost can't be mandatory sa.setOptionalTrigger(true); decider = sa.getActivatingPlayer(); } - SpellAbility ability = sa; - while (ability != null) { - final TargetRestrictions tgt = ability.getTargetRestrictions(); - - if (tgt != null) { - tgt.setMandatory(true); - } - ability = ability.getSubAbility(); - } - final boolean isMandatory = mand; - final WrappedAbility wrapperAbility = new WrappedAbility(regtrig, sa, decider); - wrapperAbility.setTrigger(true); - wrapperAbility.setMandatory(isMandatory); //wrapperAbility.setDescription(wrapperAbility.getStackDescription()); //wrapperAbility.setDescription(wrapperAbility.toUnsuppressedString()); diff --git a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java index 68ee4cdd483..e996bfa30fa 100644 --- a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java +++ b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java @@ -33,8 +33,9 @@ public class WrappedAbility extends Ability { boolean mandatory = false; public WrappedAbility(final Trigger regtrig0, final SpellAbility sa0, final Player decider0) { - super(regtrig0.getHostCard(), ManaCost.ZERO, sa0.getView()); + super(sa0.getHostCard(), ManaCost.ZERO, sa0.getView()); setTrigger(regtrig0); + setTrigger(true); sa = sa0; decider = decider0; sa.setDescription(this.getStackDescription()); @@ -53,18 +54,6 @@ public class WrappedAbility extends Ability { return decider; } - public final void setMandatory(final boolean mand) { - this.mandatory = mand; - } - - /** - * @return the mandatory - */ - @Override - public boolean isMandatory() { - return mandatory; - } - @Override public String getParam(String key) { return sa.getParam(key); } @@ -214,7 +203,7 @@ public class WrappedAbility extends Ability { @Override public String toUnsuppressedString() { String desc = this.getStackDescription(); /* use augmented stack description as string for wrapped things */ - String card = getTrigger().getHostCard().toString(); + String card = getHostCard().toString(); if ( !desc.contains(card) && desc.contains(" this ")) { /* a hack for Evolve and similar that don't have CARDNAME */ return card + ": " + desc; } else return desc; @@ -446,9 +435,8 @@ public class WrappedAbility extends Ability { public void resolve() { final Game game = sa.getActivatingPlayer().getGame(); final Trigger regtrig = getTrigger(); - Map triggerParams = regtrig.getMapParams(); - if (!(regtrig instanceof TriggerAlways) && !triggerParams.containsKey("NoResolvingCheck")) { + if (!(regtrig instanceof TriggerAlways) && !regtrig.hasParam("NoResolvingCheck")) { // Most State triggers don't have "Intervening If" if (!regtrig.requirementsCheck(game)) { return; @@ -460,10 +448,10 @@ public class WrappedAbility extends Ability { } } - if (triggerParams.containsKey("ResolvingCheck")) { + if (regtrig.hasParam("ResolvingCheck")) { // rare cases: Hidden Predators (state trigger, but have "Intervening If" to check IsPresent2) etc. Map recheck = new HashMap<>(); - String key = triggerParams.get("ResolvingCheck"); + String key = regtrig.getParam("ResolvingCheck"); String value = regtrig.getParam(key); recheck.put(key, value); if (!meetsCommonRequirements(recheck)) { @@ -471,29 +459,18 @@ public class WrappedAbility extends Ability { } } - TriggerHandler th = game.getTriggerHandler(); - // set Trigger sa.setTrigger(regtrig); - if (decider != null && !decider.getController().confirmTrigger(this, triggerParams, this.isMandatory())) { + if (decider != null && !decider.getController().confirmTrigger(this)) { return; } - if (!triggerParams.containsKey("NoTimestampCheck")) { + if (!regtrig.hasParam("NoTimestampCheck")) { timestampCheck(); } getActivatingPlayer().getController().playSpellAbilityNoStack(sa, false); - - // Add eventual delayed trigger. - if (triggerParams.containsKey("DelayedTrigger")) { - final String sVarName = triggerParams.get("DelayedTrigger"); - final Trigger deltrig = TriggerHandler.parseTrigger(regtrig.getHostCard().getSVar(sVarName), - regtrig.getHostCard(), true); - deltrig.setStoredTriggeredObjects(this.getTriggeringObjects()); - th.registerDelayedTrigger(deltrig); - } } /** diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java index 0d55b936008..b4d0ef9dc7f 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java @@ -197,7 +197,7 @@ public class PlayerControllerForTests extends PlayerController { } @Override - public boolean confirmTrigger(WrappedAbility wrapper, Map triggerParams, boolean isMandatory) { + public boolean confirmTrigger(WrappedAbility wrapper) { return true; } diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 1053ceddca4..3675ca588c5 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -622,8 +622,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont } @Override - public boolean confirmTrigger(final WrappedAbility wrapper, final Map triggerParams, - final boolean isMandatory) { + public boolean confirmTrigger(final WrappedAbility wrapper) { final SpellAbility sa = wrapper.getWrappedAbility(); final Trigger regtrig = wrapper.getTrigger(); if (getGui().shouldAlwaysAcceptTrigger(regtrig.getId())) { @@ -645,8 +644,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont // append trigger description unless prompt is compact or detailed // descriptions are on buildQuestion.append("\n("); - buildQuestion.append(TextUtil.fastReplace(triggerParams.get("TriggerDescription"), - "CARDNAME", regtrig.getHostCard().getName())); + buildQuestion.append(regtrig.toString()); buildQuestion.append(")"); } final Map tos = sa.getTriggeringObjects(); diff --git a/forge-gui/src/main/java/forge/player/TargetSelection.java b/forge-gui/src/main/java/forge/player/TargetSelection.java index dc4c7a7a418..d9230e11b1e 100644 --- a/forge-gui/src/main/java/forge/player/TargetSelection.java +++ b/forge-gui/src/main/java/forge/player/TargetSelection.java @@ -65,6 +65,10 @@ public class TargetSelection { private boolean bTargetingDone = false; + private boolean isMandatory() { + return ability.isMandatory() || getTgt().getMandatory(); + } + public final boolean chooseTargets(Integer numTargets) { final TargetRestrictions tgt = getTgt(); final boolean canTarget = tgt != null && tgt.doesTarget(); @@ -97,13 +101,13 @@ public class TargetSelection { // Cancel ability if there aren't any valid Candidates return false; } - if (tgt.getMandatory() && !hasCandidates && hasEnoughTargets) { + if (isMandatory() && !hasCandidates && hasEnoughTargets) { // Mandatory target selection, that has no candidates but enough targets (Min == 0, but no choices) return true; } final List zones = tgt.getZone(); - final boolean mandatory = tgt.getMandatory() && hasCandidates; + final boolean mandatory = isMandatory() && hasCandidates; final boolean choiceResult; final boolean random = tgt.isRandomTarget();