From 1e10d49d0f56edcb23e0eae07663d558907e3a16 Mon Sep 17 00:00:00 2001
From: Adam Pantel <>
Date: Mon, 5 Apr 2021 17:51:26 -0400
Subject: [PATCH] Strict Proctor
---
.../java/forge/game/ability/AbilityKey.java | 2 +
.../game/trigger/TriggerAbilityTriggered.java | 131 ++++++++++++++++++
.../forge/game/trigger/TriggerHandler.java | 6 +-
.../java/forge/game/trigger/TriggerType.java | 1 +
.../main/java/forge/game/zone/MagicStack.java | 7 +
.../cardsfolder/upcoming/strict_proctor.txt | 8 ++
6 files changed, 151 insertions(+), 4 deletions(-)
create mode 100644 forge-game/src/main/java/forge/game/trigger/TriggerAbilityTriggered.java
create mode 100644 forge-gui/res/cardsfolder/upcoming/strict_proctor.txt
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java
index 12b138c40f6..b3f4f97dd62 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java
@@ -75,6 +75,7 @@ public enum AbilityKey {
LifeGained("LifeGained"),
Mana("Mana"),
MergedCards("MergedCards"),
+ Mode("Mode"),
MonstrosityAmount("MonstrosityAmount"),
NewCard("NewCard"),
NewCounterAmount("NewCounterAmount"),
@@ -118,6 +119,7 @@ public enum AbilityKey {
Token("Token"),
TokenNum("TokenNum"),
Transformer("Transformer"),
+ TriggeredParams("TriggeredParams"),
Vehicle("Vehicle"),
Won("Won");
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAbilityTriggered.java b/forge-game/src/main/java/forge/game/trigger/TriggerAbilityTriggered.java
new file mode 100644
index 00000000000..4ac73391b5a
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerAbilityTriggered.java
@@ -0,0 +1,131 @@
+/*
+ * Forge: Play Magic: the Gathering.
+ * Copyright (C) 2011 Forge Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package forge.game.trigger;
+
+import com.google.common.collect.ImmutableList;
+import forge.game.Game;
+import forge.game.ability.AbilityKey;
+import forge.game.card.Card;
+import forge.game.card.CardZoneTable;
+import forge.game.spellability.SpellAbility;
+import forge.game.zone.ZoneType;
+import forge.util.Localizer;
+
+import java.util.*;
+
+/**
+ *
+ * TriggerAbilityTriggered class.
+ *
+ *
+ * @author Forge
+ * @version $Id$
+ */
+public class TriggerAbilityTriggered extends Trigger {
+
+ public TriggerAbilityTriggered(final Map params, final Card host, final boolean intrinsic) {
+ super(params, host, intrinsic);
+ }
+
+ /** {@inheritDoc}
+ * @param runParams*/
+ @Override
+ public final boolean performTest(final Map runParams) {
+ final SpellAbility spellAbility = (SpellAbility) runParams.get(AbilityKey.SpellAbility);
+ if (spellAbility == null) {
+ System.out.println("TriggerAbilityTriggered performTest encountered spellAbility == null. runParams2 = " + runParams);
+ return false;
+ }
+ final Card source = spellAbility.getHostCard();
+ final Iterable causes = (Iterable) runParams.get(AbilityKey.Cause);
+ final Game game = source.getGame();
+
+ if (hasParam("ValidMode")) {
+ List validModes = Arrays.asList(getParam("ValidMode").split(","));
+ String mode = (String) runParams.get(AbilityKey.Mode);
+ if (!validModes.contains(mode)) {
+ return false;
+ }
+ }
+
+ if (hasParam("ValidDestination")) {
+ List validDestinations = Arrays.asList(getParam("ValidDestination").split(","));
+ List destinations = Arrays.asList(((String)runParams.get(AbilityKey.Destination)).split(","));
+ if (Collections.disjoint(validDestinations, destinations)) {
+ return false;
+ }
+ }
+
+ if (!matchesValidParam("ValidSource", source)) {
+ return false;
+ }
+
+ if (hasParam("ValidCause")) {
+ boolean match = false;
+ for (Card cause : causes) {
+ if(matchesValidParam("ValidCause", cause)) {
+ match = true;
+ }
+ }
+ if (!match) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ public final void setTriggeringObjects(final SpellAbility sa, Map runParams) {
+ final SpellAbility triggeredSA = (SpellAbility) runParams.get(AbilityKey.SpellAbility);
+ sa.setTriggeringObject(AbilityKey.Source, triggeredSA.getHostCard());
+ sa.setTriggeringObjectsFrom(
+ runParams,
+ AbilityKey.SpellAbility,
+ AbilityKey.Cause);
+ }
+
+ @Override
+ public String getImportantStackObjects(SpellAbility sa) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Localizer.getInstance().getMessage("lblSpellAbility")).append(": ").append(sa.getTriggeringObject(AbilityKey.SpellAbility));
+ return sb.toString();
+ }
+
+ public static void addTriggeringObject(Trigger regtrig, SpellAbility sa, Map runParams) {
+ Map newRunParams = AbilityKey.newMap();
+ newRunParams.put(AbilityKey.Mode, regtrig.getMode().toString());
+ if (regtrig.getMode() == TriggerType.ChangesZone) {
+ newRunParams.put(AbilityKey.Destination, runParams.get(AbilityKey.Destination));
+ newRunParams.put(AbilityKey.Cause, ImmutableList.of(runParams.get(AbilityKey.Card)));
+ } else if (regtrig.getMode() == TriggerType.ChangesZoneAll) {
+ final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards);
+ Set destinations = new HashSet<>();
+ for (ZoneType dest : ZoneType.values()) {
+ if (table.containsColumn(dest) && !table.column(dest).isEmpty()) {
+ destinations.add(dest.toString());
+ }
+ }
+ newRunParams.put(AbilityKey.Destination, String.join(",", destinations));
+ newRunParams.put(AbilityKey.Cause, table.allCards());
+ }
+ sa.setTriggeringObject(AbilityKey.TriggeredParams, newRunParams);
+ }
+}
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 f7946d52b1b..867ed21fed0 100644
--- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java
@@ -17,10 +17,7 @@
*/
package forge.game.trigger;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
@@ -557,6 +554,7 @@ public class TriggerHandler {
sa.setTrigger(regtrig);
sa.setSourceTrigger(regtrig.getId());
regtrig.setTriggeringObjects(sa, runParams);
+ TriggerAbilityTriggered.addTriggeringObject(regtrig, sa, runParams);
sa.setTriggerRemembered(regtrig.getTriggerRemembered());
if (regtrig.hasParam("TriggerController")) {
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerType.java b/forge-game/src/main/java/forge/game/trigger/TriggerType.java
index 21e5c5f99e9..affa61ccdb4 100644
--- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java
@@ -14,6 +14,7 @@ import forge.game.card.Card;
public enum TriggerType {
Abandoned(TriggerAbandoned.class),
AbilityCast(TriggerSpellAbilityCastOrCopy.class),
+ AbilityTriggered(TriggerAbilityTriggered.class),
Adapt(TriggerAdapt.class),
Always(TriggerAlways.class),
Attached(TriggerAttached.class),
diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java
index 6bc8329400a..58be16c0c8a 100644
--- a/forge-game/src/main/java/forge/game/zone/MagicStack.java
+++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java
@@ -332,6 +332,13 @@ public class MagicStack /* extends MyObservable */ implements Iterable newRunParams = (Map) sp.getTriggeringObject(AbilityKey.TriggeredParams);
+ newRunParams.put(AbilityKey.SpellAbility, sp);
+ game.getTriggerHandler().runTrigger(TriggerType.AbilityTriggered, newRunParams, false);
+ }
} else {
// Run Copy triggers
if (sp.isSpell()) {
diff --git a/forge-gui/res/cardsfolder/upcoming/strict_proctor.txt b/forge-gui/res/cardsfolder/upcoming/strict_proctor.txt
new file mode 100644
index 00000000000..8265feb2f8a
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/strict_proctor.txt
@@ -0,0 +1,8 @@
+Name:Strict Proctor
+ManaCost:1 W
+Types:Creature Spirit Cleric
+PT:1/3
+K:Flying
+T:Mode$ AbilityTriggered | ValidDestination$ Battlefield | ValidMode$ ChangesZone,ChangesZoneAll | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ Whenever a permanent entering the battlefield causes a triggered ability to trigger, counter that ability unless its controller pays {2}.
+SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility | UnlessCost$ 2 | UnlessPayer$ TriggeredSpellAbilityController
+Oracle:Flying\nWhenever a permanent entering the battlefield causes a triggered ability to trigger, counter that ability unless its controller pays {2}.