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

@@ -23,7 +23,6 @@ import com.google.common.collect.Lists;
import forge.ai.ability.AnimateAi; import forge.ai.ability.AnimateAi;
import forge.card.CardTypeView; import forge.card.CardTypeView;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.ability.effects.ProtectEffect; import forge.game.ability.effects.ProtectEffect;
@@ -196,12 +195,12 @@ public class AiAttackController {
for (Card c : ai.getOpponents().getCardsIn(ZoneType.Battlefield)) { for (Card c : ai.getOpponents().getCardsIn(ZoneType.Battlefield)) {
for (Trigger t : c.getTriggers()) { for (Trigger t : c.getTriggers()) {
if (t.getMode() == TriggerType.Attacks) { if (t.getMode() == TriggerType.Attacks) {
SpellAbility sa = t.getOverridingAbility(); SpellAbility sa = t.ensureAbility();
if (sa == null && t.hasParam("Execute")) { if (sa == null) {
sa = AbilityFactory.getAbility(c, t.getParam("Execute")); continue;
} }
if (sa != null && sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("DefinedPlayers"))) { if (sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("DefinedPlayers"))) {
List<Card> valid = CardLists.getValidCards(c.getController().getCreaturesInPlay(), sa.getParam("ValidCards"), c.getController(), c, sa); List<Card> valid = CardLists.getValidCards(c.getController().getCreaturesInPlay(), sa.getParam("ValidCards"), c.getController(), c, sa);
// TODO: this assumes that 1 damage is dealt per creature. Improve this to check the parameter/X to determine // TODO: this assumes that 1 damage is dealt per creature. Improve this to check the parameter/X to determine
// how much damage is dealt by each of the creatures in the valid list. // how much damage is dealt by each of the creatures in the valid list.
@@ -1349,9 +1348,9 @@ public class AiAttackController {
if (!TriggerType.Exerted.equals(t.getMode())) { if (!TriggerType.Exerted.equals(t.getMode())) {
continue; continue;
} }
SpellAbility sa = t.getOverridingAbility(); SpellAbility sa = t.ensureAbility();
if (sa == null) { if (sa == null) {
sa = AbilityFactory.getAbility(c, t.getParam("Execute")); continue;
} }
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.setActivatingPlayer(c.getController()); sa.setActivatingPlayer(c.getController());

View File

@@ -28,7 +28,6 @@ import forge.card.ColorSet;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
import forge.game.*; import forge.game.*;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
@@ -1468,15 +1467,13 @@ public class ComputerUtil {
// Triggered abilities // Triggered abilities
if (c.isCreature() && c.isInZone(ZoneType.Battlefield) && CombatUtil.canAttack(c)) { if (c.isCreature() && c.isInZone(ZoneType.Battlefield) && CombatUtil.canAttack(c)) {
for (final Trigger t : c.getTriggers()) { for (final Trigger t : c.getTriggers()) {
if ("Attacks".equals(t.getParam("Mode")) && t.hasParam("Execute")) { if (TriggerType.Attacks.equals(t.getMode())) {
String exec = c.getSVar(t.getParam("Execute")); SpellAbility sa = t.ensureAbility();
if (!exec.isEmpty()) { if (sa == null) {
SpellAbility trigSa = AbilityFactory.getAbility(exec, c); continue;
if (trigSa != null && trigSa.getApi() == ApiType.LoseLife }
&& trigSa.getParamOrDefault("Defined", "").contains("Opponent")) { if (sa.getApi() == ApiType.LoseLife && sa.getParamOrDefault("Defined", "").contains("Opponent")) {
trigSa.setHostCard(c); damage += AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("LifeAmount"), sa);
damage += AbilityUtils.calculateAmount(trigSa.getHostCard(), trigSa.getParam("LifeAmount"), trigSa);
}
} }
} }
} }
@@ -2612,7 +2609,6 @@ public class ComputerUtil {
theTriggers.addAll(c.getTriggers()); theTriggers.addAll(c.getTriggers());
} }
for (Trigger trigger : theTriggers) { for (Trigger trigger : theTriggers) {
Map<String, String> trigParams = trigger.getMapParams();
final Card source = trigger.getHostCard(); final Card source = trigger.getHostCard();
@@ -2622,73 +2618,43 @@ public class ComputerUtil {
if (!trigger.requirementsCheck(game)) { if (!trigger.requirementsCheck(game)) {
continue; continue;
} }
TriggerType mode = trigger.getMode(); if (trigger.getMode() != TriggerType.SpellCast) {
if (mode != TriggerType.SpellCast) {
continue; continue;
} }
if (trigParams.containsKey("ValidCard")) { if (trigger.hasParam("ValidCard")) {
if (!card.isValid(trigParams.get("ValidCard"), source.getController(), source, sa)) { if (!card.isValid(trigger.getParam("ValidCard"), source.getController(), source, sa)) {
continue; continue;
} }
} }
if (trigParams.containsKey("ValidActivatingPlayer")) { if (trigger.hasParam("ValidActivatingPlayer")) {
if (!player.isValid(trigParams.get("ValidActivatingPlayer"), source.getController(), source, sa)) { if (!player.isValid(trigger.getParam("ValidActivatingPlayer"), source.getController(), source, sa)) {
continue; continue;
} }
} }
if (!trigParams.containsKey("Execute")) { // fall back for OverridingAbility
// fall back for OverridingAbility SpellAbility trigSa = trigger.ensureAbility();
SpellAbility trigSa = trigger.getOverridingAbility(); if (trigSa == null) {
if (trigSa == null) { continue;
}
if (trigSa.getApi() == ApiType.DealDamage) {
if (!"TriggeredActivator".equals(trigSa.getParam("Defined"))) {
continue; continue;
} }
if (trigSa.getApi() == ApiType.DealDamage) { if (!trigSa.hasParam("NumDmg")) {
if (!"TriggeredActivator".equals(trigSa.getParam("Defined"))) {
continue;
}
if (!trigSa.hasParam("NumDmg")) {
continue;
}
damage += ComputerUtilCombat.predictDamageTo(player,
AbilityUtils.calculateAmount(source, trigSa.getParam("NumDmg"), trigSa), source, false);
} else if (trigSa.getApi() == ApiType.LoseLife) {
if (!"TriggeredActivator".equals(trigSa.getParam("Defined"))) {
continue;
}
if (!trigSa.hasParam("LifeAmount")) {
continue;
}
damage += AbilityUtils.calculateAmount(source, trigSa.getParam("LifeAmount"), trigSa);
}
} else {
String ability = source.getSVar(trigParams.get("Execute"));
if (ability.isEmpty()) {
continue; continue;
} }
damage += ComputerUtilCombat.predictDamageTo(player,
final Map<String, String> abilityParams = AbilityFactory.getMapParams(ability); AbilityUtils.calculateAmount(source, trigSa.getParam("NumDmg"), trigSa), source, false);
if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage")) } else if (trigSa.getApi() == ApiType.LoseLife) {
|| (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) { if (!"TriggeredActivator".equals(trigSa.getParam("Defined"))) {
if (!"TriggeredActivator".equals(abilityParams.get("Defined"))) { continue;
continue;
}
if (!abilityParams.containsKey("NumDmg")) {
continue;
}
damage += ComputerUtilCombat.predictDamageTo(player,
AbilityUtils.calculateAmount(source, abilityParams.get("NumDmg"), null), source, false);
} else if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("LoseLife"))
|| (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("LoseLife"))) {
if (!"TriggeredActivator".equals(abilityParams.get("Defined"))) {
continue;
}
if (!abilityParams.containsKey("LifeAmount")) {
continue;
}
damage += AbilityUtils.calculateAmount(source, abilityParams.get("LifeAmount"), null);
} }
if (!trigSa.hasParam("LifeAmount")) {
continue;
}
damage += AbilityUtils.calculateAmount(source, trigSa.getParam("LifeAmount"), trigSa);
} }
} }
@@ -2704,7 +2670,6 @@ public class ComputerUtil {
theTriggers.addAll(card.getTriggers()); theTriggers.addAll(card.getTriggers());
} }
for (Trigger trigger : theTriggers) { for (Trigger trigger : theTriggers) {
Map<String, String> trigParams = trigger.getMapParams();
final Card source = trigger.getHostCard(); final Card source = trigger.getHostCard();
@@ -2714,74 +2679,43 @@ public class ComputerUtil {
if (!trigger.requirementsCheck(game)) { if (!trigger.requirementsCheck(game)) {
continue; continue;
} }
if (trigParams.containsKey("CheckOnTriggeredCard") if (trigger.hasParam("CheckOnTriggeredCard")
&& AbilityUtils.getDefinedCards(permanent, source.getSVar(trigParams.get("CheckOnTriggeredCard").split(" ")[0]), null).isEmpty()) { && AbilityUtils.getDefinedCards(permanent, source.getSVar(trigger.getParam("CheckOnTriggeredCard").split(" ")[0]), null).isEmpty()) {
continue; continue;
} }
TriggerType mode = trigger.getMode(); if (trigger.getMode() != TriggerType.ChangesZone) {
if (mode != TriggerType.ChangesZone) {
continue; continue;
} }
if (!"Battlefield".equals(trigParams.get("Destination"))) { if (!"Battlefield".equals(trigger.getParam("Destination"))) {
continue; continue;
} }
if (trigParams.containsKey("ValidCard")) { if (trigger.hasParam("ValidCard")) {
if (!permanent.isValid(trigParams.get("ValidCard"), source.getController(), source, null)) { if (!permanent.isValid(trigger.getParam("ValidCard"), source.getController(), source, null)) {
continue; continue;
} }
} }
if (!trigParams.containsKey("Execute")) { // fall back for OverridingAbility
// fall back for OverridingAbility SpellAbility trigSa = trigger.ensureAbility();
SpellAbility trigSa = trigger.getOverridingAbility(); if (trigSa == null) {
if (trigSa == null) { continue;
}
if (trigSa.getApi() == ApiType.DealDamage) {
if (!"TriggeredCardController".equals(trigSa.getParam("Defined"))) {
continue; continue;
} }
if (trigSa.getApi() == ApiType.DealDamage) { if (!trigSa.hasParam("NumDmg")) {
if (!"TriggeredCardController".equals(trigSa.getParam("Defined"))) {
continue;
}
if (!trigSa.hasParam("NumDmg")) {
continue;
}
damage += ComputerUtilCombat.predictDamageTo(player,
AbilityUtils.calculateAmount(source, trigSa.getParam("NumDmg"), trigSa), source, false);
} else if (trigSa.getApi() == ApiType.LoseLife) {
if (!"TriggeredCardController".equals(trigSa.getParam("Defined"))) {
continue;
}
if (!trigSa.hasParam("LifeAmount")) {
continue;
}
damage += AbilityUtils.calculateAmount(source, trigSa.getParam("LifeAmount"), trigSa);
}
} else {
String ability = source.getSVar(trigParams.get("Execute"));
if (ability.isEmpty()) {
continue; continue;
} }
damage += ComputerUtilCombat.predictDamageTo(player,
final Map<String, String> abilityParams = AbilityFactory.getMapParams(ability); AbilityUtils.calculateAmount(source, trigSa.getParam("NumDmg"), trigSa), source, false);
// Destroy triggers } else if (trigSa.getApi() == ApiType.LoseLife) {
if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage")) if (!"TriggeredCardController".equals(trigSa.getParam("Defined"))) {
|| (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) { continue;
if (!"TriggeredCardController".equals(abilityParams.get("Defined"))) {
continue;
}
if (!abilityParams.containsKey("NumDmg")) {
continue;
}
damage += ComputerUtilCombat.predictDamageTo(player,
AbilityUtils.calculateAmount(source, abilityParams.get("NumDmg"), null), source, false);
} else if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("LoseLife"))
|| (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("LoseLife"))) {
if (!"TriggeredCardController".equals(abilityParams.get("Defined"))) {
continue;
}
if (!abilityParams.containsKey("LifeAmount")) {
continue;
}
damage += AbilityUtils.calculateAmount(source, abilityParams.get("LifeAmount"), null);
} }
if (!trigSa.hasParam("LifeAmount")) {
continue;
}
damage += AbilityUtils.calculateAmount(source, trigSa.getParam("LifeAmount"), trigSa);
} }
} }
return damage; return damage;

View File

@@ -14,7 +14,6 @@ import forge.deck.Deck;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.game.Game; import forge.game.Game;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.*; import forge.game.card.*;
@@ -697,39 +696,21 @@ public class ComputerUtilCard {
} }
// same for Trigger that does make Tokens // same for Trigger that does make Tokens
for(Trigger t:c.getTriggers()){ for(Trigger t:c.getTriggers()){
SpellAbility sa = t.getOverridingAbility(); SpellAbility sa = t.ensureAbility();
String sTokenTypes = null;
if (sa != null) { if (sa != null) {
if (sa.getApi() != ApiType.Token || !sa.hasParam("TokenTypes")) { if (sa.getApi() != ApiType.Token || !sa.hasParam("TokenTypes")) {
continue; continue;
} }
sTokenTypes = sa.getParam("TokenTypes"); for (String var : sa.getParam("TokenTypes").split(",")) {
} else if (t.hasParam("Execute")) { if (!CardType.isACreatureType(var)) {
String name = t.getParam("Execute"); continue;
if (!c.hasSVar(name)) { }
continue; Integer count = typesInDeck.get(var);
if (count == null) {
count = 0;
}
typesInDeck.put(var, count + 1);
} }
Map<String, String> params = AbilityFactory.getMapParams(c.getSVar(name));
if (!params.containsKey("TokenTypes")) {
continue;
}
sTokenTypes = params.get("TokenTypes");
}
if (sTokenTypes == null) {
continue;
}
for (String var : sTokenTypes.split(",")) {
if (!CardType.isACreatureType(var)) {
continue;
}
Integer count = typesInDeck.get(var);
if (count == null) {
count = 0;
}
typesInDeck.put(var, count + 1);
} }
} }
// special rule for Fabricate and Servo // special rule for Fabricate and Servo

View File

@@ -28,7 +28,6 @@ import com.google.common.collect.Maps;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
@@ -46,7 +45,6 @@ import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
@@ -1274,44 +1272,26 @@ public class ComputerUtilCombat {
} }
for (final Trigger trigger : theTriggers) { for (final Trigger trigger : theTriggers) {
final Map<String, String> trigParams = trigger.getMapParams();
final Card source = trigger.getHostCard(); final Card source = trigger.getHostCard();
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)) { if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)) {
continue; continue;
} }
Map<String, String> abilityParams = null; SpellAbility sa = trigger.ensureAbility();
if (trigger.getOverridingAbility() != null) { if (sa == null) {
abilityParams = trigger.getOverridingAbility().getMapParams();
} else if (trigParams.containsKey("Execute")) {
final String ability = source.getSVar(trigParams.get("Execute"));
abilityParams = AbilityFactory.getMapParams(ability);
} else {
continue; continue;
} }
if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) { if (sa.usesTargeting()) {
continue; // targeted pumping not supported continue; // targeted pumping not supported
} }
if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump")
&& !abilityParams.get("AB").equals("PumpAll")) { if (!ApiType.Pump.equals(sa.getApi()) && !ApiType.PumpAll.equals(sa.getApi())) {
continue;
}
if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump")
&& !abilityParams.get("DB").equals("PumpAll")) {
continue; continue;
} }
if (abilityParams.containsKey("Cost")) { if (sa.hasParam("Cost")) {
SpellAbility sa = null;
if (trigger.getOverridingAbility() != null) {
sa = trigger.getOverridingAbility();
} else {
final String ability = source.getSVar(trigParams.get("Execute"));
sa = AbilityFactory.getAbility(ability, source);
}
sa.setActivatingPlayer(source.getController()); sa.setActivatingPlayer(source.getController());
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) { if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
continue; continue;
@@ -1319,15 +1299,15 @@ public class ComputerUtilCombat {
} }
List<Card> list = Lists.newArrayList(); List<Card> list = Lists.newArrayList();
if (!abilityParams.containsKey("ValidCards")) { if (!sa.hasParam("ValidCards")) {
list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null); list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null);
} }
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredAttacker")) { if (sa.hasParam("Defined") && sa.getParam("Defined").equals("TriggeredAttacker")) {
list.add(attacker); list.add(attacker);
} }
if (abilityParams.containsKey("ValidCards")) { if (sa.hasParam("ValidCards")) {
if (attacker.isValid(abilityParams.get("ValidCards").split(","), source.getController(), source, null) if (attacker.isValid(sa.getParam("ValidCards").split(","), source.getController(), source, null)
|| attacker.isValid(abilityParams.get("ValidCards").replace("attacking+", "").split(","), || attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","),
source.getController(), source, null)) { source.getController(), source, null)) {
list.add(attacker); list.add(attacker);
} }
@@ -1338,11 +1318,11 @@ public class ComputerUtilCombat {
if (!list.contains(attacker)) { if (!list.contains(attacker)) {
continue; continue;
} }
if (!abilityParams.containsKey("NumAtt")) { if (!sa.hasParam("NumAtt")) {
continue; continue;
} }
String att = abilityParams.get("NumAtt"); String att = sa.getParam("NumAtt");
if (att.startsWith("+")) { if (att.startsWith("+")) {
att = att.substring(1); att = att.substring(1);
} }
@@ -1657,35 +1637,26 @@ public class ComputerUtilCombat {
theTriggers.addAll(card.getTriggers()); theTriggers.addAll(card.getTriggers());
} }
for (Trigger trigger : theTriggers) { for (Trigger trigger : theTriggers) {
Map<String, String> trigParams = trigger.getMapParams();
final Card source = trigger.getHostCard(); final Card source = trigger.getHostCard();
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) { if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
continue; continue;
} }
//consider delayed triggers SpellAbility sa = trigger.ensureAbility();
if (trigParams.containsKey("DelayedTrigger")) { if (sa == null) {
String sVarName = trigParams.get("DelayedTrigger");
trigger = TriggerHandler.parseTrigger(source.getSVar(sVarName), trigger.getHostCard(), true);
trigParams = trigger.getMapParams();
}
if (!trigParams.containsKey("Execute")) {
continue; continue;
} }
String ability = source.getSVar(trigParams.get("Execute")); if (ApiType.Destroy.equals(sa.getApi())) {
final Map<String, String> abilityParams = AbilityFactory.getMapParams(ability); if (!sa.hasParam("Defined")) {
if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("Destroy"))
|| (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("Destroy"))) {
if (!abilityParams.containsKey("Defined")) {
continue; continue;
} }
if (abilityParams.get("Defined").equals("TriggeredAttacker")) { if (sa.getParam("Defined").equals("TriggeredAttacker")) {
return true; return true;
} }
if (abilityParams.get("Defined").equals("Self") && source.equals(attacker)) { if (sa.getParam("Defined").equals("Self") && source.equals(attacker)) {
return true; return true;
} }
if (abilityParams.get("Defined").equals("TriggeredTarget") && source.equals(blocker)) { if (sa.getParam("Defined").equals("TriggeredTarget") && source.equals(blocker)) {
return true; return true;
} }
} }
@@ -1935,36 +1906,27 @@ public class ComputerUtilCombat {
theTriggers.addAll(card.getTriggers()); theTriggers.addAll(card.getTriggers());
} }
for (Trigger trigger : theTriggers) { for (Trigger trigger : theTriggers) {
Map<String, String> trigParams = trigger.getMapParams();
final Card source = trigger.getHostCard(); final Card source = trigger.getHostCard();
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) { if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
continue; continue;
} }
//consider delayed triggers SpellAbility sa = trigger.ensureAbility();
if (trigParams.containsKey("DelayedTrigger")) { if (sa == null) {
String sVarName = trigParams.get("DelayedTrigger");
trigger = TriggerHandler.parseTrigger(source.getSVar(sVarName), trigger.getHostCard(), true);
trigParams = trigger.getMapParams();
}
if (!trigParams.containsKey("Execute")) {
continue; continue;
} }
String ability = source.getSVar(trigParams.get("Execute"));
final Map<String, String> abilityParams = AbilityFactory.getMapParams(ability);
// Destroy triggers // Destroy triggers
if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("Destroy")) if (ApiType.Destroy.equals(sa.getApi())) {
|| (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("Destroy"))) { if (!sa.hasParam("Defined")) {
if (!abilityParams.containsKey("Defined")) {
continue; continue;
} }
if (abilityParams.get("Defined").equals("TriggeredBlocker")) { if (sa.getParam("Defined").equals("TriggeredBlocker")) {
return true; return true;
} }
if (abilityParams.get("Defined").equals("Self") && source.equals(blocker)) { if (sa.getParam("Defined").equals("Self") && source.equals(blocker)) {
return true; return true;
} }
if (abilityParams.get("Defined").equals("TriggeredTarget") && source.equals(attacker)) { if (sa.getParam("Defined").equals("TriggeredTarget") && source.equals(attacker)) {
return true; return true;
} }
} }
@@ -2578,26 +2540,24 @@ public class ComputerUtilCombat {
// Test for some special triggers that can change the creature in combat // Test for some special triggers that can change the creature in combat
for (Trigger t : attacker.getTriggers()) { for (Trigger t : attacker.getTriggers()) {
if (t.getMode() == TriggerType.Attacks && t.hasParam("Execute")) { if (t.getMode() == TriggerType.Attacks) {
if (!attacker.hasSVar(t.getParam("Execute"))) { SpellAbility exec = t.ensureAbility();
if (exec == null) {
continue; continue;
} }
SpellAbility exec = AbilityFactory.getAbility(attacker, t.getParam("Execute")); if (exec.getApi() == ApiType.Clone && "Self".equals(exec.getParam("CloneTarget"))
if (exec != null) { && exec.hasParam("ValidTgts") && exec.getParam("ValidTgts").contains("Creature")
if (exec.getApi() == ApiType.Clone && "Self".equals(exec.getParam("CloneTarget")) && exec.getParam("ValidTgts").contains("attacking")) {
&& exec.hasParam("ValidTgts") && exec.getParam("ValidTgts").contains("Creature") // Tilonalli's Skinshifter and potentially other similar cards that can clone other stuff
&& exec.getParam("ValidTgts").contains("attacking")) { // while attacking
// Tilonalli's Skinshifter and potentially other similar cards that can clone other stuff if (exec.getParam("ValidTgts").contains("nonLegendary") && attacker.getType().isLegendary()) {
// while attacking continue;
if (exec.getParam("ValidTgts").contains("nonLegendary") && attacker.getType().isLegendary()) { }
continue; int maxPwr = 0;
} for (Card c : attacker.getController().getCreaturesInPlay()) {
int maxPwr = 0; if (c.getNetPower() > maxPwr || (c.getNetPower() == maxPwr && ComputerUtilCard.evaluateCreature(c) > ComputerUtilCard.evaluateCreature(attackerAfterTrigs))) {
for (Card c : attacker.getController().getCreaturesInPlay()) { maxPwr = c.getNetPower();
if (c.getNetPower() > maxPwr || (c.getNetPower() == maxPwr && ComputerUtilCard.evaluateCreature(c) > ComputerUtilCard.evaluateCreature(attackerAfterTrigs))) { attackerAfterTrigs = c;
maxPwr = c.getNetPower();
attackerAfterTrigs = c;
}
} }
} }
} }

View File

@@ -26,7 +26,6 @@ import forge.card.MagicColor;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
import forge.game.GameType; import forge.game.GameType;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.*; import forge.game.card.*;
@@ -636,10 +635,7 @@ public class SpecialCardAi {
boolean canRetFromGrave = false; boolean canRetFromGrave = false;
String name = c.getName().replace(',', ';'); String name = c.getName().replace(',', ';');
for (Trigger t : c.getTriggers()) { for (Trigger t : c.getTriggers()) {
SpellAbility ab = null; SpellAbility ab = t.ensureAbility();
if (t.hasParam("Execute")) {
ab = AbilityFactory.getAbility(c.getSVar(t.getParam("Execute")), c);
}
if (ab == null) { continue; } if (ab == null) { continue; }
if (ab.getApi() == ApiType.ChangeZone if (ab.getApi() == ApiType.ChangeZone

View File

@@ -1086,12 +1086,7 @@ public class AttachAi extends SpellAbilityAi {
final Map<String, String> params = t.getMapParams(); final Map<String, String> params = t.getMapParams();
if ("Card.Self".equals(params.get("ValidCard")) if ("Card.Self".equals(params.get("ValidCard"))
&& "Battlefield".equals(params.get("Destination"))) { && "Battlefield".equals(params.get("Destination"))) {
SpellAbility trigSa = null; SpellAbility trigSa = t.ensureAbility();
if (t.hasParam("Execute") && attachSource.hasSVar(t.getParam("Execute"))) {
trigSa = AbilityFactory.getAbility(attachSource.getSVar(params.get("Execute")), attachSource);
} else if (t.getOverridingAbility() != null) {
trigSa = t.getOverridingAbility();
}
if (trigSa != null && trigSa.getApi() == ApiType.DealDamage && "Enchanted".equals(trigSa.getParam("Defined"))) { if (trigSa != null && trigSa.getApi() == ApiType.DealDamage && "Enchanted".equals(trigSa.getParam("Defined"))) {
for (Card target : list) { for (Card target : list) {
if (!target.getController().isOpponentOf(ai)) { if (!target.getController().isOpponentOf(ai)) {

View File

@@ -1811,7 +1811,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
} else // This is an intrinsic effect that blinks the card (e.g. Obzedat, Ghost Council), no need to } else // This is an intrinsic effect that blinks the card (e.g. Obzedat, Ghost Council), no need to
// return the commander to the Command zone. // return the commander to the Command zone.
if (subApi == ApiType.DelayedTrigger) { if (subApi == ApiType.DelayedTrigger) {
SpellAbility exec = causeSub.getAdditionalAbility("Execute"); SpellAbility exec = causeSub.getAdditionalAbility("Execute");
if (exec != null && exec.getApi() == ApiType.ChangeZone) { if (exec != null && exec.getApi() == ApiType.ChangeZone) {
// A blink effect implemented using a delayed trigger // A blink effect implemented using a delayed trigger
return !"Exile".equals(exec.getParam("Origin")) || !"Battlefield".equals(exec.getParam("Destination")); return !"Exile".equals(exec.getParam("Origin")) || !"Battlefield".equals(exec.getParam("Destination"));

View File

@@ -3,7 +3,6 @@ package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import forge.ai.*; import forge.ai.*;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.ability.AbilityFactory;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardLists; import forge.game.card.CardLists;
@@ -22,11 +21,9 @@ public class DelayedTriggerAi extends SpellAbilityAi {
// TODO: improve ai // TODO: improve ai
return true; return true;
} }
SpellAbility trigsa = null; SpellAbility trigsa = sa.getAdditionalAbility("Execute");
if (sa.hasAdditionalAbility("Execute")) { if (trigsa == null) {
trigsa = sa.getAdditionalAbility("Execute"); return false;
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
} }
trigsa.setActivatingPlayer(ai); trigsa.setActivatingPlayer(ai);
@@ -39,12 +36,11 @@ public class DelayedTriggerAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
SpellAbility trigsa = null; SpellAbility trigsa = sa.getAdditionalAbility("Execute");
if (sa.hasAdditionalAbility("Execute")) { if (trigsa == null) {
trigsa = sa.getAdditionalAbility("Execute"); return false;
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
} }
AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai); trigsa.setActivatingPlayer(ai);
@@ -143,11 +139,9 @@ public class DelayedTriggerAi extends SpellAbilityAi {
} }
// Generic logic // Generic logic
SpellAbility trigsa = null; SpellAbility trigsa = sa.getAdditionalAbility("Execute");
if (sa.hasAdditionalAbility("Execute")) { if (trigsa == null) {
trigsa = sa.getAdditionalAbility("Execute"); return false;
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
} }
trigsa.setActivatingPlayer(ai); trigsa.setActivatingPlayer(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa); return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);

View File

@@ -3,6 +3,7 @@ package forge.ai.ability;
import forge.ai.*; import forge.ai.*;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
@@ -193,7 +194,7 @@ public class FightAi extends SpellAbilityAi {
for (Card humanCreature : humCreatures) { for (Card humanCreature : humCreatures) {
for (Card aiCreature : aiCreatures) { for (Card aiCreature : aiCreatures) {
if (source.isSpell()) { // heroic triggers adding counters and prowess if (source.isSpell()) { // heroic triggers adding counters and prowess
final int bonus = getSpellBonus(aiCreature); final int bonus = getSpellBonus(aiCreature);
power += bonus; power += bonus;
toughness += bonus; toughness += bonus;
} }
@@ -247,28 +248,32 @@ public class FightAi extends SpellAbilityAi {
return false; return false;
} }
/** /**
* Compute the bonus from Heroic +1/+1 counters or Prowess * Compute the bonus from Heroic +1/+1 counters or Prowess
*/ */
private static int getSpellBonus(final Card aiCreature) { private static int getSpellBonus(final Card aiCreature) {
for (Trigger t : aiCreature.getTriggers()) { for (Trigger t : aiCreature.getTriggers()) {
if (t.getMode() == TriggerType.SpellCast) { if (t.getMode() == TriggerType.SpellCast) {
final Map<String, String> params = t.getMapParams(); SpellAbility sa = t.ensureAbility();
if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer")) final Map<String, String> params = t.getMapParams();
&& params.containsKey("Execute")) { if (sa == null) {
SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature); continue;
if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) { }
return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic); if (ApiType.PutCounter.equals(sa.getApi())) {
} if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
break; SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
} if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
if ("ProwessPump".equals(params.get("Execute"))) { return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
return 1; }
} break;
} }
} } else if (ApiType.Pump.equals(sa.getApi())) {
return 0; // TODO add prowess boost
} }
}
}
return 0;
}
private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) { private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
if (canKill(fighter, opponent, pumpAttack)) { if (canKill(fighter, opponent, pumpAttack)) {

View File

@@ -1,7 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.*; import forge.ai.*;
import forge.game.ability.AbilityFactory;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -16,12 +15,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
return true; return true;
} }
SpellAbility trigsa = null; SpellAbility trigsa = sa.getAdditionalAbility("Execute");
if (sa.hasAdditionalAbility("Execute")) { if (trigsa == null) {
trigsa = sa.getAdditionalAbility("Execute"); return false;
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
} }
trigsa.setActivatingPlayer(ai); trigsa.setActivatingPlayer(ai);
if (trigsa instanceof AbilitySub) { if (trigsa instanceof AbilitySub) {
@@ -33,12 +31,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
SpellAbility trigsa = null; SpellAbility trigsa = sa.getAdditionalAbility("Execute");
if (sa.hasAdditionalAbility("Execute")) { if (trigsa == null) {
trigsa = sa.getAdditionalAbility("Execute"); return false;
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
} }
AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai); trigsa.setActivatingPlayer(ai);
@@ -56,12 +53,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
return true; return true;
} }
SpellAbility trigsa = null; SpellAbility trigsa = sa.getAdditionalAbility("Execute");
if (sa.hasAdditionalAbility("Execute")) { if (trigsa == null) {
trigsa = sa.getAdditionalAbility("Execute"); return false;
} else {
trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
} }
trigsa.setActivatingPlayer(ai); trigsa.setActivatingPlayer(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa); return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
} }

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"))); 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."; + " exile it instead of putting it anywhere else.";
String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone; 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.setLayer(ReplacementLayer.Other);
re.setOverridingAbility(AbilityFactory.getAbility(effect, eff)); re.setOverridingAbility(AbilityFactory.getAbility(effect, eff));

View File

@@ -159,8 +159,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
// Grant triggers // Grant triggers
final List<Trigger> addedTriggers = Lists.newArrayList(); final List<Trigger> addedTriggers = Lists.newArrayList();
for (final String s : triggers) { for (final String s : triggers) {
final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false); final Trigger parsedTrigger = TriggerHandler.parseTrigger(AbilityUtils.getSVar(sa, s), c, false, sa);
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(c, parsedTrigger.getParam("Execute"), sa));
parsedTrigger.setOriginalHost(source); parsedTrigger.setOriginalHost(source);
addedTriggers.add(parsedTrigger); addedTriggers.add(parsedTrigger);
} }
@@ -168,7 +167,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
// give replacement effects // give replacement effects
final List<ReplacementEffect> addedReplacements = Lists.newArrayList(); final List<ReplacementEffect> addedReplacements = Lists.newArrayList();
for (final String s : replacements) { 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 // 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); Card lki = CardUtil.getLKICopy(gameCard);
lki.clearControllers(); lki.clearControllers();
lki.setOwner(sa.getActivatingPlayer()); 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)); delTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true));
if (triggerRemembered != null) { 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); AbilitySub overridingSA = (AbilitySub)sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false);
// need to reset the parent, additionalAbility does set it to this // need to reset the parent, additionalAbility does set it to this
overridingSA.setParent(null); overridingSA.setParent(null);
@@ -96,7 +96,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
delTrig.setOverridingAbility(overridingSA); delTrig.setOverridingAbility(overridingSA);
} }
final TriggerHandler trigHandler = sa.getActivatingPlayer().getGame().getTriggerHandler(); final TriggerHandler trigHandler = game.getTriggerHandler();
if (mapParams.containsKey("DelayedTriggerDefinedPlayer")) { // on sb's next turn if (mapParams.containsKey("DelayedTriggerDefinedPlayer")) { // on sb's next turn
Player p = Iterables.getFirst(AbilityUtils.getDefinedPlayers(sa.getHostCard(), mapParams.get("DelayedTriggerDefinedPlayer"), sa), null); Player p = Iterables.getFirst(AbilityUtils.getDefinedPlayers(sa.getHostCard(), mapParams.get("DelayedTriggerDefinedPlayer"), sa), null);
trigHandler.registerPlayerDefinedDelayedTrigger(p, delTrig); trigHandler.registerPlayerDefinedDelayedTrigger(p, delTrig);

View File

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

View File

@@ -57,7 +57,7 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
Card lki = CardUtil.getLKICopy(gameCard); Card lki = CardUtil.getLKICopy(gameCard);
lki.clearControllers(); lki.clearControllers();
lki.setOwner(sa.getActivatingPlayer()); 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)); immediateTrig.setSpawningAbility(sa.copy(lki, sa.getActivatingPlayer(), true));
// Need to copy paid costs // 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); AbilitySub overridingSA = (AbilitySub)sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false);
// need to set Parent to null, otherwise it might have wrong root ability // need to set Parent to null, otherwise it might have wrong root ability
overridingSA.setParent(null); overridingSA.setParent(null);
@@ -85,9 +85,8 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
immediateTrig.setOverridingAbility(overridingSA); 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 // 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) { public boolean hasETBTrigger(final boolean drawbackOnly) {
for (final Trigger tr : getTriggers()) { for (final Trigger tr : getTriggers()) {
final Map<String, String> params = tr.getMapParams();
if (tr.getMode() != TriggerType.ChangesZone) { if (tr.getMode() != TriggerType.ChangesZone) {
continue; continue;
} }
if (!params.get("Destination").equals(ZoneType.Battlefield.toString())) { if (!tr.getParam("Destination").equals(ZoneType.Battlefield.toString())) {
continue; continue;
} }
if (params.containsKey("ValidCard") && !params.get("ValidCard").contains("Self")) { if (tr.hasParam("ValidCard") && !tr.getParam("ValidCard").contains("Self")) {
continue; continue;
} }
if (drawbackOnly && params.containsKey("Execute")){ if (drawbackOnly) {
String exec = this.getSVar(params.get("Execute")); SpellAbility sa = tr.ensureAbility();
if (exec.contains("AB$")) { if (sa == null || sa.isActivatedAbility()) {
continue; continue;
} }
} }

View File

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

View File

@@ -2227,7 +2227,7 @@ public class CardFactoryUtil {
final String abStringAfflict = "DB$ LoseLife | Defined$ TriggeredDefendingPlayer" + final String abStringAfflict = "DB$ LoseLife | Defined$ TriggeredDefendingPlayer" +
" | LifeAmount$ " + n; " | 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)); afflictTrigger.setOverridingAbility(AbilityFactory.getAbility(abStringAfflict, card));
inst.addTrigger(afflictTrigger); inst.addTrigger(afflictTrigger);

View File

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

View File

@@ -781,8 +781,7 @@ public final class StaticAbilityContinuous {
// add Replacement effects // add Replacement effects
if (addReplacements != null) { if (addReplacements != null) {
for (String rep : addReplacements) { for (String rep : addReplacements) {
final ReplacementEffect actualRep = ReplacementHandler.parseReplacement(rep, affectedCard, false); final ReplacementEffect actualRep = ReplacementHandler.parseReplacement(rep, affectedCard, false, stAb);
actualRep.setIntrinsic(false);
addedReplacementEffects.add(actualRep); addedReplacementEffects.add(actualRep);
} }
} }
@@ -790,17 +789,12 @@ public final class StaticAbilityContinuous {
// add triggers // add triggers
if (addTriggers != null) { if (addTriggers != null) {
for (final String trigger : addTriggers) { 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 // if the trigger has Execute param, which most trigger gained by Static Abilties should have
// turn them into SpellAbility object before adding to card // turn them into SpellAbility object before adding to card
// with that the TargetedCard does not need the Svars added to them anymore // 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 // 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.setOriginalHost(hostCard);
actualTrigger.setIntrinsic(false);
addedTrigger.add(actualTrigger); addedTrigger.add(actualTrigger);
} }
} }

View File

@@ -19,6 +19,7 @@ package forge.game.trigger;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.IHasSVars;
import forge.game.TriggerReplacementBase; import forge.game.TriggerReplacementBase;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey; 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(); SpellAbility sa = getOverridingAbility();
if (sa == null && hasParam("Execute")) { if (sa == null && hasParam("Execute")) {
sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute")); sa = AbilityFactory.getAbility(getHostCard(), getParam("Execute"), sVarHolder);
setOverridingAbility(sa); setOverridingAbility(sa);
} }
return 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.Game;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.IHasSVars;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; 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) { 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 { try {
final Map<String, String> mapParams = TriggerHandler.parseParams(trigParse); final Map<String, String> mapParams = TriggerHandler.parseParams(trigParse);
return TriggerHandler.parseTrigger(mapParams, host, intrinsic); return TriggerHandler.parseTrigger(mapParams, host, intrinsic, sVarHolder);
} catch (Exception e) { } catch (Exception e) {
String msg = "TriggerHandler:parseTrigger failed to parse"; String msg = "TriggerHandler:parseTrigger failed to parse";
Sentry.getContext().recordBreadcrumb( 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; Trigger ret = null;
try { try {
final TriggerType type = TriggerType.smartValueOf(mapParams.get("Mode")); final TriggerType type = TriggerType.smartValueOf(mapParams.get("Mode"));
ret = type.createTrigger(mapParams, host, intrinsic); ret = type.createTrigger(mapParams, host, intrinsic);
if (sVarHolder != null) {
ret.ensureAbility(sVarHolder);
}
} catch (Exception e) { } catch (Exception e) {
String msg = "TriggerHandler:parseTrigger failed to parse"; String msg = "TriggerHandler:parseTrigger failed to parse";
Sentry.getContext().recordBreadcrumb( Sentry.getContext().recordBreadcrumb(

View File

@@ -26,7 +26,7 @@ public final class CardRelationMatrixGenerator {
public static HashMap<String,HashMap<String,List<Map.Entry<PaperCard,Integer>>>> cardPools = new HashMap<>(); public static HashMap<String,HashMap<String,List<Map.Entry<PaperCard,Integer>>>> cardPools = new HashMap<>();
public static Map<String, Map<String,List<List<String>>>> ldaPools = new HashMap(); public static Map<String, Map<String,List<List<String>>>> ldaPools = new HashMap<>();
/** /**
To ensure that only cards with at least 14 connections (as 14*4+4=60) are included in the card based deck To ensure that only cards with at least 14 connections (as 14*4+4=60) are included in the card based deck
generation pools generation pools