Trigger & Replacement: ensure abilities

This commit is contained in:
Hans Mackowiak
2021-02-12 12:21:50 +01:00
parent 53e1d766f2
commit 8217adbc32
24 changed files with 213 additions and 342 deletions

View File

@@ -249,7 +249,7 @@ public final class AbilityFactory {
}
}
if (api == ApiType.DelayedTrigger && mapParams.containsKey("Execute")) {
if ((api == ApiType.DelayedTrigger || api == ApiType.ImmediateTrigger) && mapParams.containsKey("Execute")) {
spellAbility.setSVar(mapParams.get("Execute"), sVarHolder.getSVar(mapParams.get("Execute")));
}

View File

@@ -429,7 +429,7 @@ public abstract class SpellAbilityEffect {
+ " exile it instead of putting it anywhere else.";
String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone;
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true, null);
re.setLayer(ReplacementLayer.Other);
re.setOverridingAbility(AbilityFactory.getAbility(effect, eff));

View File

@@ -159,8 +159,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
// Grant triggers
final List<Trigger> addedTriggers = Lists.newArrayList();
for (final String s : triggers) {
final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false);
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(c, parsedTrigger.getParam("Execute"), sa));
final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa);
parsedTrigger.setOriginalHost(source);
addedTriggers.add(parsedTrigger);
}
@@ -168,7 +167,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
// give replacement effects
final List<ReplacementEffect> addedReplacements = Lists.newArrayList();
for (final String s : replacements) {
addedReplacements.add(ReplacementHandler.parseReplacement(AbilityUtils.getSVar(sa, s), c, false));
addedReplacements.add(ReplacementHandler.parseReplacement(AbilityUtils.getSVar(sa, s), c, false, sa));
}
// give static abilities (should only be used by cards to give

View File

@@ -58,7 +58,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
Card lki = CardUtil.getLKICopy(gameCard);
lki.clearControllers();
lki.setOwner(sa.getActivatingPlayer());
final Trigger delTrig = TriggerHandler.parseTrigger(mapParams, lki, sa.isIntrinsic());
final Trigger delTrig = TriggerHandler.parseTrigger(mapParams, lki, sa.isIntrinsic(), null);
delTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true));
if (triggerRemembered != null) {
@@ -81,7 +81,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
}
}
if (mapParams.containsKey("Execute") || sa.hasAdditionalAbility("Execute")) {
if (sa.hasAdditionalAbility("Execute")) {
AbilitySub overridingSA = (AbilitySub)sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false);
// need to reset the parent, additionalAbility does set it to this
overridingSA.setParent(null);
@@ -96,7 +96,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
delTrig.setOverridingAbility(overridingSA);
}
final TriggerHandler trigHandler = sa.getActivatingPlayer().getGame().getTriggerHandler();
final TriggerHandler trigHandler = game.getTriggerHandler();
if (mapParams.containsKey("DelayedTriggerDefinedPlayer")) { // on sb's next turn
Player p = Iterables.getFirst(AbilityUtils.getDefinedPlayers(sa.getHostCard(), mapParams.get("DelayedTriggerDefinedPlayer"), sa), null);
trigHandler.registerPlayerDefinedDelayedTrigger(p, delTrig);

View File

@@ -189,7 +189,7 @@ public class EffectEffect extends SpellAbilityEffect {
for (final String s : effectReplacementEffects) {
final String actualReplacement = AbilityUtils.getSVar(sa, s);
final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement, eff, true);
final ReplacementEffect parsedReplacement = ReplacementHandler.parseReplacement(actualReplacement, eff, true, sa);
parsedReplacement.setActiveZone(EnumSet.of(ZoneType.Command));
parsedReplacement.setIntrinsic(true);
eff.addReplacementEffect(parsedReplacement);

View File

@@ -57,7 +57,7 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
Card lki = CardUtil.getLKICopy(gameCard);
lki.clearControllers();
lki.setOwner(sa.getActivatingPlayer());
final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, lki, sa.isIntrinsic());
final Trigger immediateTrig = TriggerHandler.parseTrigger(mapParams, lki, sa.isIntrinsic(), null);
immediateTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true));
// Need to copy paid costs
@@ -74,7 +74,7 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
}
}
if (mapParams.containsKey("Execute") || sa.hasAdditionalAbility("Execute")) {
if (sa.hasAdditionalAbility("Execute")) {
AbilitySub overridingSA = (AbilitySub)sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false);
// need to set Parent to null, otherwise it might have wrong root ability
overridingSA.setParent(null);
@@ -85,9 +85,8 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
immediateTrig.setOverridingAbility(overridingSA);
}
final TriggerHandler trigHandler = sa.getActivatingPlayer().getGame().getTriggerHandler();
// Instead of registering this, add to the delayed triggers as an immediate trigger type? Which means it'll fire as soon as possible
trigHandler.registerDelayedTrigger(immediateTrig);
game.getTriggerHandler().registerDelayedTrigger(immediateTrig);
}
}

View File

@@ -5932,21 +5932,20 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public boolean hasETBTrigger(final boolean drawbackOnly) {
for (final Trigger tr : getTriggers()) {
final Map<String, String> params = tr.getMapParams();
if (tr.getMode() != TriggerType.ChangesZone) {
continue;
}
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) {
if (!tr.getParam("Destination").equals(ZoneType.Battlefield.toString())) {
continue;
}
if (params.containsKey("ValidCard") && !params.get("ValidCard").contains("Self")) {
if (tr.hasParam("ValidCard") && !tr.getParam("ValidCard").contains("Self")) {
continue;
}
if (drawbackOnly && params.containsKey("Execute")){
String exec = this.getSVar(params.get("Execute"));
if (exec.contains("AB$")) {
if (drawbackOnly) {
SpellAbility sa = tr.ensureAbility();
if (sa == null || sa.isActivatedAbility()) {
continue;
}
}

View File

@@ -664,6 +664,7 @@ public class CardFactory {
if (origSVars.containsKey(s)) {
final String actualTrigger = origSVars.get(s);
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true);
parsedTrigger.setOriginalHost(host);
state.addTrigger(parsedTrigger);
}
}
@@ -687,6 +688,7 @@ public class CardFactory {
if (origSVars.containsKey(s)) {
final String actualAbility = origSVars.get(s);
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, out);
grantedAbility.setOriginalHost(host);
grantedAbility.setIntrinsic(true);
state.addSpellAbility(grantedAbility);
}
@@ -700,6 +702,7 @@ public class CardFactory {
if (origSVars.containsKey(s)) {
final String actualStatic = origSVars.get(s);
final StaticAbility grantedStatic = new StaticAbility(actualStatic, out);
grantedStatic.setOriginalHost(host);
grantedStatic.setIntrinsic(true);
state.addStaticAbility(grantedStatic);
}

View File

@@ -2227,7 +2227,7 @@ public class CardFactoryUtil {
final String abStringAfflict = "DB$ LoseLife | Defined$ TriggeredDefendingPlayer" +
" | LifeAmount$ " + n;
final Trigger afflictTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
final Trigger afflictTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic, null);
afflictTrigger.setOverridingAbility(AbilityFactory.getAbility(abStringAfflict, card));
inst.addTrigger(afflictTrigger);

View File

@@ -19,6 +19,7 @@ package forge.game.replacement;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.IHasSVars;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
@@ -370,7 +371,10 @@ public class ReplacementHandler {
* @return A finished instance
*/
public static ReplacementEffect parseReplacement(final String repParse, final Card host, final boolean intrinsic) {
return ReplacementHandler.parseReplacement(parseParams(repParse), host, intrinsic);
return parseReplacement(repParse, host, intrinsic, host);
}
public static ReplacementEffect parseReplacement(final String repParse, final Card host, final boolean intrinsic, final IHasSVars sVarHolder) {
return ReplacementHandler.parseReplacement(parseParams(repParse), host, intrinsic, sVarHolder);
}
public static Map<String, String> parseParams(final String repParse) {
@@ -388,7 +392,7 @@ public class ReplacementHandler {
* The card that hosts the replacement effect
* @return The finished instance
*/
private static ReplacementEffect parseReplacement(final Map<String, String> mapParams, final Card host, final boolean intrinsic) {
private static ReplacementEffect parseReplacement(final Map<String, String> mapParams, final Card host, final boolean intrinsic, final IHasSVars sVarHolder) {
final ReplacementType rt = ReplacementType.smartValueOf(mapParams.get("Event"));
ReplacementEffect ret = rt.createReplacement(mapParams, host, intrinsic);
@@ -397,8 +401,8 @@ public class ReplacementHandler {
ret.setActiveZone(EnumSet.copyOf(ZoneType.listValueOf(activeZones)));
}
if (mapParams.containsKey("ReplaceWith")) {
ret.setOverridingAbility(AbilityFactory.getAbility(host, mapParams.get("ReplaceWith"), ret));
if (mapParams.containsKey("ReplaceWith") && sVarHolder != null) {
ret.setOverridingAbility(AbilityFactory.getAbility(host, mapParams.get("ReplaceWith"), sVarHolder));
}
return ret;

View File

@@ -781,8 +781,7 @@ public final class StaticAbilityContinuous {
// add Replacement effects
if (addReplacements != null) {
for (String rep : addReplacements) {
final ReplacementEffect actualRep = ReplacementHandler.parseReplacement(rep, affectedCard, false);
actualRep.setIntrinsic(false);
final ReplacementEffect actualRep = ReplacementHandler.parseReplacement(rep, affectedCard, false, stAb);
addedReplacementEffects.add(actualRep);
}
}
@@ -790,17 +789,12 @@ public final class StaticAbilityContinuous {
// add triggers
if (addTriggers != null) {
for (final String trigger : addTriggers) {
final Trigger actualTrigger = TriggerHandler.parseTrigger(trigger, affectedCard, false);
final Trigger actualTrigger = TriggerHandler.parseTrigger(trigger, affectedCard, false, stAb);
// if the trigger has Execute param, which most trigger gained by Static Abilties should have
// turn them into SpellAbility object before adding to card
// with that the TargetedCard does not need the Svars added to them anymore
// but only do it if the trigger doesn't already have a overriding ability
if (actualTrigger.hasParam("Execute") && actualTrigger.getOverridingAbility() == null) {
// set overriding ability to the trigger
actualTrigger.setOverridingAbility(AbilityFactory.getAbility(affectedCard, actualTrigger.getParam("Execute"), stAb));
}
actualTrigger.setOriginalHost(hostCard);
actualTrigger.setIntrinsic(false);
addedTrigger.add(actualTrigger);
}
}

View File

@@ -19,6 +19,7 @@ package forge.game.trigger;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.IHasSVars;
import forge.game.TriggerReplacementBase;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
@@ -562,12 +563,16 @@ public abstract class Trigger extends TriggerReplacementBase {
}
}
public SpellAbility ensureAbility() {
public SpellAbility ensureAbility(final IHasSVars sVarHolder) {
SpellAbility sa = getOverridingAbility();
if (sa == null && hasParam("Execute")) {
sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute"));
sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute"), sVarHolder);
setOverridingAbility(sa);
}
return sa;
}
public SpellAbility ensureAbility() {
return ensureAbility(this);
}
}

View File

@@ -19,6 +19,7 @@ package forge.game.trigger;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.IHasSVars;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
@@ -123,9 +124,13 @@ public class TriggerHandler {
}
public static Trigger parseTrigger(final String trigParse, final Card host, final boolean intrinsic) {
return parseTrigger(trigParse, host, intrinsic, host);
}
public static Trigger parseTrigger(final String trigParse, final Card host, final boolean intrinsic, final IHasSVars sVarHolder) {
try {
final Map<String, String> mapParams = TriggerHandler.parseParams(trigParse);
return TriggerHandler.parseTrigger(mapParams, host, intrinsic);
return TriggerHandler.parseTrigger(mapParams, host, intrinsic, sVarHolder);
} catch (Exception e) {
String msg = "TriggerHandler:parseTrigger failed to parse";
Sentry.getContext().recordBreadcrumb(
@@ -137,12 +142,15 @@ public class TriggerHandler {
}
}
public static Trigger parseTrigger(final Map<String, String> mapParams, final Card host, final boolean intrinsic) {
public static Trigger parseTrigger(final Map<String, String> mapParams, final Card host, final boolean intrinsic, final IHasSVars sVarHolder) {
Trigger ret = null;
try {
final TriggerType type = TriggerType.smartValueOf(mapParams.get("Mode"));
ret = type.createTrigger(mapParams, host, intrinsic);
if (sVarHolder != null) {
ret.ensureAbility(sVarHolder);
}
} catch (Exception e) {
String msg = "TriggerHandler:parseTrigger failed to parse";
Sentry.getContext().recordBreadcrumb(