diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java
index 63b58c2ff25..0920da8b4d8 100644
--- a/forge-ai/src/main/java/forge/ai/AiAttackController.java
+++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java
@@ -23,7 +23,6 @@ import com.google.common.collect.Lists;
import forge.ai.ability.AnimateAi;
import forge.card.CardTypeView;
import forge.game.GameEntity;
-import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.ability.effects.ProtectEffect;
@@ -196,12 +195,12 @@ public class AiAttackController {
for (Card c : ai.getOpponents().getCardsIn(ZoneType.Battlefield)) {
for (Trigger t : c.getTriggers()) {
if (t.getMode() == TriggerType.Attacks) {
- SpellAbility sa = t.getOverridingAbility();
- if (sa == null && t.hasParam("Execute")) {
- sa = AbilityFactory.getAbility(c, t.getParam("Execute"));
+ SpellAbility sa = t.ensureAbility();
+ if (sa == null) {
+ continue;
}
- if (sa != null && sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("DefinedPlayers"))) {
+ if (sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("DefinedPlayers"))) {
List 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
// 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())) {
continue;
}
- SpellAbility sa = t.getOverridingAbility();
+ SpellAbility sa = t.ensureAbility();
if (sa == null) {
- sa = AbilityFactory.getAbility(c, t.getParam("Execute"));
+ continue;
}
if (sa.usesTargeting()) {
sa.setActivatingPlayer(c.getController());
diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java
index fe64fcdebf6..28453966716 100644
--- a/forge-ai/src/main/java/forge/ai/AiController.java
+++ b/forge-ai/src/main/java/forge/ai/AiController.java
@@ -429,8 +429,7 @@ public class AiController {
byte color = MagicColor.fromName(c);
for (Card land : landList) {
for (final SpellAbility m : ComputerUtilMana.getAIPlayableMana(land)) {
- AbilityManaPart mp = m.getManaPart();
- if (mp.canProduce(MagicColor.toShortString(color), m)) {
+ if (m.canProduce(MagicColor.toShortString(color))) {
return land;
}
}
@@ -483,8 +482,7 @@ public class AiController {
return land;
}
for (final SpellAbility m : ComputerUtilMana.getAIPlayableMana(land)) {
- AbilityManaPart mp = m.getManaPart();
- if (mp.canProduce(MagicColor.toShortString(color), m)) {
+ if (m.canProduce(MagicColor.toShortString(color))) {
return land;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
index 4424b60a1bb..e51e0d847a4 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
@@ -28,7 +28,6 @@ import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCostShard;
import forge.game.*;
-import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
@@ -1468,15 +1467,13 @@ public class ComputerUtil {
// Triggered abilities
if (c.isCreature() && c.isInZone(ZoneType.Battlefield) && CombatUtil.canAttack(c)) {
for (final Trigger t : c.getTriggers()) {
- if ("Attacks".equals(t.getParam("Mode")) && t.hasParam("Execute")) {
- String exec = c.getSVar(t.getParam("Execute"));
- if (!exec.isEmpty()) {
- SpellAbility trigSa = AbilityFactory.getAbility(exec, c);
- if (trigSa != null && trigSa.getApi() == ApiType.LoseLife
- && trigSa.getParamOrDefault("Defined", "").contains("Opponent")) {
- trigSa.setHostCard(c);
- damage += AbilityUtils.calculateAmount(trigSa.getHostCard(), trigSa.getParam("LifeAmount"), trigSa);
- }
+ if (TriggerType.Attacks.equals(t.getMode())) {
+ SpellAbility sa = t.ensureAbility();
+ if (sa == null) {
+ continue;
+ }
+ if (sa.getApi() == ApiType.LoseLife && sa.getParamOrDefault("Defined", "").contains("Opponent")) {
+ damage += AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("LifeAmount"), sa);
}
}
}
@@ -2075,9 +2072,8 @@ public class ComputerUtil {
for (Card c : lands) {
for (SpellAbility sa : c.getManaAbilities()) {
- AbilityManaPart abmana = sa.getManaPart();
for (byte col : MagicColor.WUBRG) {
- if (abmana.canProduce(MagicColor.toLongString(col))) {
+ if (sa.canProduce(MagicColor.toLongString(col))) {
numProducers.get(col).add(c);
}
}
@@ -2613,7 +2609,6 @@ public class ComputerUtil {
theTriggers.addAll(c.getTriggers());
}
for (Trigger trigger : theTriggers) {
- Map trigParams = trigger.getMapParams();
final Card source = trigger.getHostCard();
@@ -2623,73 +2618,43 @@ public class ComputerUtil {
if (!trigger.requirementsCheck(game)) {
continue;
}
- TriggerType mode = trigger.getMode();
- if (mode != TriggerType.SpellCast) {
+ if (trigger.getMode() != TriggerType.SpellCast) {
continue;
}
- if (trigParams.containsKey("ValidCard")) {
- if (!card.isValid(trigParams.get("ValidCard"), source.getController(), source, sa)) {
+ if (trigger.hasParam("ValidCard")) {
+ if (!card.isValid(trigger.getParam("ValidCard"), source.getController(), source, sa)) {
continue;
}
}
- if (trigParams.containsKey("ValidActivatingPlayer")) {
- if (!player.isValid(trigParams.get("ValidActivatingPlayer"), source.getController(), source, sa)) {
+ if (trigger.hasParam("ValidActivatingPlayer")) {
+ if (!player.isValid(trigger.getParam("ValidActivatingPlayer"), source.getController(), source, sa)) {
continue;
}
}
- if (!trigParams.containsKey("Execute")) {
- // fall back for OverridingAbility
- SpellAbility trigSa = trigger.getOverridingAbility();
- if (trigSa == null) {
+ // fall back for OverridingAbility
+ SpellAbility trigSa = trigger.ensureAbility();
+ if (trigSa == null) {
+ continue;
+ }
+ if (trigSa.getApi() == ApiType.DealDamage) {
+ if (!"TriggeredActivator".equals(trigSa.getParam("Defined"))) {
continue;
}
- if (trigSa.getApi() == ApiType.DealDamage) {
- 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()) {
+ if (!trigSa.hasParam("NumDmg")) {
continue;
}
-
- final Map abilityParams = AbilityFactory.getMapParams(ability);
- if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage"))
- || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) {
- if (!"TriggeredActivator".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 (!"TriggeredActivator".equals(abilityParams.get("Defined"))) {
- continue;
- }
- if (!abilityParams.containsKey("LifeAmount")) {
- continue;
- }
- damage += AbilityUtils.calculateAmount(source, abilityParams.get("LifeAmount"), null);
+ 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);
}
}
@@ -2705,7 +2670,6 @@ public class ComputerUtil {
theTriggers.addAll(card.getTriggers());
}
for (Trigger trigger : theTriggers) {
- Map trigParams = trigger.getMapParams();
final Card source = trigger.getHostCard();
@@ -2715,74 +2679,43 @@ public class ComputerUtil {
if (!trigger.requirementsCheck(game)) {
continue;
}
- if (trigParams.containsKey("CheckOnTriggeredCard")
- && AbilityUtils.getDefinedCards(permanent, source.getSVar(trigParams.get("CheckOnTriggeredCard").split(" ")[0]), null).isEmpty()) {
+ if (trigger.hasParam("CheckOnTriggeredCard")
+ && AbilityUtils.getDefinedCards(permanent, source.getSVar(trigger.getParam("CheckOnTriggeredCard").split(" ")[0]), null).isEmpty()) {
continue;
}
- TriggerType mode = trigger.getMode();
- if (mode != TriggerType.ChangesZone) {
+ if (trigger.getMode() != TriggerType.ChangesZone) {
continue;
}
- if (!"Battlefield".equals(trigParams.get("Destination"))) {
+ if (!"Battlefield".equals(trigger.getParam("Destination"))) {
continue;
}
- if (trigParams.containsKey("ValidCard")) {
- if (!permanent.isValid(trigParams.get("ValidCard"), source.getController(), source, null)) {
+ if (trigger.hasParam("ValidCard")) {
+ if (!permanent.isValid(trigger.getParam("ValidCard"), source.getController(), source, null)) {
continue;
}
}
- if (!trigParams.containsKey("Execute")) {
- // fall back for OverridingAbility
- SpellAbility trigSa = trigger.getOverridingAbility();
- if (trigSa == null) {
+ // fall back for OverridingAbility
+ SpellAbility trigSa = trigger.ensureAbility();
+ if (trigSa == null) {
+ continue;
+ }
+ if (trigSa.getApi() == ApiType.DealDamage) {
+ if (!"TriggeredCardController".equals(trigSa.getParam("Defined"))) {
continue;
}
- if (trigSa.getApi() == ApiType.DealDamage) {
- 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()) {
+ if (!trigSa.hasParam("NumDmg")) {
continue;
}
-
- final Map abilityParams = AbilityFactory.getMapParams(ability);
- // Destroy triggers
- if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage"))
- || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) {
- 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);
+ 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);
}
}
return damage;
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
index 69b9191e6b4..109c5dfc423 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
@@ -14,7 +14,6 @@ import forge.deck.Deck;
import forge.deck.DeckSection;
import forge.game.Game;
import forge.game.GameObject;
-import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
@@ -697,39 +696,21 @@ public class ComputerUtilCard {
}
// same for Trigger that does make Tokens
for(Trigger t:c.getTriggers()){
- SpellAbility sa = t.getOverridingAbility();
- String sTokenTypes = null;
+ SpellAbility sa = t.ensureAbility();
if (sa != null) {
if (sa.getApi() != ApiType.Token || !sa.hasParam("TokenTypes")) {
continue;
}
- sTokenTypes = sa.getParam("TokenTypes");
- } else if (t.hasParam("Execute")) {
- String name = t.getParam("Execute");
- if (!c.hasSVar(name)) {
- continue;
+ for (String var : sa.getParam("TokenTypes").split(",")) {
+ if (!CardType.isACreatureType(var)) {
+ continue;
+ }
+ Integer count = typesInDeck.get(var);
+ if (count == null) {
+ count = 0;
+ }
+ typesInDeck.put(var, count + 1);
}
-
- Map 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
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
index 753aaf17989..fe240b141db 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
@@ -28,7 +28,6 @@ import com.google.common.collect.Maps;
import forge.game.CardTraitBase;
import forge.game.Game;
import forge.game.GameEntity;
-import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
@@ -46,7 +45,6 @@ import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
-import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
@@ -1274,44 +1272,26 @@ public class ComputerUtilCombat {
}
for (final Trigger trigger : theTriggers) {
- final Map trigParams = trigger.getMapParams();
final Card source = trigger.getHostCard();
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)) {
continue;
}
- Map abilityParams = null;
- if (trigger.getOverridingAbility() != null) {
- abilityParams = trigger.getOverridingAbility().getMapParams();
- } else if (trigParams.containsKey("Execute")) {
- final String ability = source.getSVar(trigParams.get("Execute"));
- abilityParams = AbilityFactory.getMapParams(ability);
- } else {
+ SpellAbility sa = trigger.ensureAbility();
+ if (sa == null) {
continue;
}
- if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) {
+ if (sa.usesTargeting()) {
continue; // targeted pumping not supported
}
- if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump")
- && !abilityParams.get("AB").equals("PumpAll")) {
- continue;
- }
- if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump")
- && !abilityParams.get("DB").equals("PumpAll")) {
+
+ if (!ApiType.Pump.equals(sa.getApi()) && !ApiType.PumpAll.equals(sa.getApi())) {
continue;
}
- if (abilityParams.containsKey("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);
- }
-
+ if (sa.hasParam("Cost")) {
sa.setActivatingPlayer(source.getController());
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
continue;
@@ -1319,15 +1299,15 @@ public class ComputerUtilCombat {
}
List list = Lists.newArrayList();
- if (!abilityParams.containsKey("ValidCards")) {
- list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
+ if (!sa.hasParam("ValidCards")) {
+ 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);
}
- if (abilityParams.containsKey("ValidCards")) {
- if (attacker.isValid(abilityParams.get("ValidCards").split(","), source.getController(), source, null)
- || attacker.isValid(abilityParams.get("ValidCards").replace("attacking+", "").split(","),
+ if (sa.hasParam("ValidCards")) {
+ if (attacker.isValid(sa.getParam("ValidCards").split(","), source.getController(), source, null)
+ || attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","),
source.getController(), source, null)) {
list.add(attacker);
}
@@ -1338,11 +1318,11 @@ public class ComputerUtilCombat {
if (!list.contains(attacker)) {
continue;
}
- if (!abilityParams.containsKey("NumAtt")) {
+ if (!sa.hasParam("NumAtt")) {
continue;
}
- String att = abilityParams.get("NumAtt");
+ String att = sa.getParam("NumAtt");
if (att.startsWith("+")) {
att = att.substring(1);
}
@@ -1657,35 +1637,26 @@ public class ComputerUtilCombat {
theTriggers.addAll(card.getTriggers());
}
for (Trigger trigger : theTriggers) {
- Map trigParams = trigger.getMapParams();
final Card source = trigger.getHostCard();
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
continue;
}
- //consider delayed triggers
- if (trigParams.containsKey("DelayedTrigger")) {
- String sVarName = trigParams.get("DelayedTrigger");
- trigger = TriggerHandler.parseTrigger(source.getSVar(sVarName), trigger.getHostCard(), true);
- trigParams = trigger.getMapParams();
- }
- if (!trigParams.containsKey("Execute")) {
+ SpellAbility sa = trigger.ensureAbility();
+ if (sa == null) {
continue;
}
- String ability = source.getSVar(trigParams.get("Execute"));
- final Map abilityParams = AbilityFactory.getMapParams(ability);
- if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("Destroy"))
- || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("Destroy"))) {
- if (!abilityParams.containsKey("Defined")) {
+ if (ApiType.Destroy.equals(sa.getApi())) {
+ if (!sa.hasParam("Defined")) {
continue;
}
- if (abilityParams.get("Defined").equals("TriggeredAttacker")) {
+ if (sa.getParam("Defined").equals("TriggeredAttacker")) {
return true;
}
- if (abilityParams.get("Defined").equals("Self") && source.equals(attacker)) {
+ if (sa.getParam("Defined").equals("Self") && source.equals(attacker)) {
return true;
}
- if (abilityParams.get("Defined").equals("TriggeredTarget") && source.equals(blocker)) {
+ if (sa.getParam("Defined").equals("TriggeredTarget") && source.equals(blocker)) {
return true;
}
}
@@ -1935,36 +1906,27 @@ public class ComputerUtilCombat {
theTriggers.addAll(card.getTriggers());
}
for (Trigger trigger : theTriggers) {
- Map trigParams = trigger.getMapParams();
final Card source = trigger.getHostCard();
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
continue;
}
- //consider delayed triggers
- if (trigParams.containsKey("DelayedTrigger")) {
- String sVarName = trigParams.get("DelayedTrigger");
- trigger = TriggerHandler.parseTrigger(source.getSVar(sVarName), trigger.getHostCard(), true);
- trigParams = trigger.getMapParams();
- }
- if (!trigParams.containsKey("Execute")) {
+ SpellAbility sa = trigger.ensureAbility();
+ if (sa == null) {
continue;
}
- String ability = source.getSVar(trigParams.get("Execute"));
- final Map abilityParams = AbilityFactory.getMapParams(ability);
// Destroy triggers
- if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("Destroy"))
- || (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("Destroy"))) {
- if (!abilityParams.containsKey("Defined")) {
+ if (ApiType.Destroy.equals(sa.getApi())) {
+ if (!sa.hasParam("Defined")) {
continue;
}
- if (abilityParams.get("Defined").equals("TriggeredBlocker")) {
+ if (sa.getParam("Defined").equals("TriggeredBlocker")) {
return true;
}
- if (abilityParams.get("Defined").equals("Self") && source.equals(blocker)) {
+ if (sa.getParam("Defined").equals("Self") && source.equals(blocker)) {
return true;
}
- if (abilityParams.get("Defined").equals("TriggeredTarget") && source.equals(attacker)) {
+ if (sa.getParam("Defined").equals("TriggeredTarget") && source.equals(attacker)) {
return true;
}
}
@@ -2578,26 +2540,24 @@ public class ComputerUtilCombat {
// Test for some special triggers that can change the creature in combat
for (Trigger t : attacker.getTriggers()) {
- if (t.getMode() == TriggerType.Attacks && t.hasParam("Execute")) {
- if (!attacker.hasSVar(t.getParam("Execute"))) {
+ if (t.getMode() == TriggerType.Attacks) {
+ SpellAbility exec = t.ensureAbility();
+ if (exec == null) {
continue;
}
- SpellAbility exec = AbilityFactory.getAbility(attacker, t.getParam("Execute"));
- if (exec != null) {
- if (exec.getApi() == ApiType.Clone && "Self".equals(exec.getParam("CloneTarget"))
- && exec.hasParam("ValidTgts") && exec.getParam("ValidTgts").contains("Creature")
- && exec.getParam("ValidTgts").contains("attacking")) {
- // Tilonalli's Skinshifter and potentially other similar cards that can clone other stuff
- // while attacking
- if (exec.getParam("ValidTgts").contains("nonLegendary") && attacker.getType().isLegendary()) {
- continue;
- }
- int maxPwr = 0;
- for (Card c : attacker.getController().getCreaturesInPlay()) {
- if (c.getNetPower() > maxPwr || (c.getNetPower() == maxPwr && ComputerUtilCard.evaluateCreature(c) > ComputerUtilCard.evaluateCreature(attackerAfterTrigs))) {
- maxPwr = c.getNetPower();
- attackerAfterTrigs = c;
- }
+ if (exec.getApi() == ApiType.Clone && "Self".equals(exec.getParam("CloneTarget"))
+ && exec.hasParam("ValidTgts") && exec.getParam("ValidTgts").contains("Creature")
+ && exec.getParam("ValidTgts").contains("attacking")) {
+ // Tilonalli's Skinshifter and potentially other similar cards that can clone other stuff
+ // while attacking
+ if (exec.getParam("ValidTgts").contains("nonLegendary") && attacker.getType().isLegendary()) {
+ continue;
+ }
+ int maxPwr = 0;
+ for (Card c : attacker.getController().getCreaturesInPlay()) {
+ if (c.getNetPower() > maxPwr || (c.getNetPower() == maxPwr && ComputerUtilCard.evaluateCreature(c) > ComputerUtilCard.evaluateCreature(attackerAfterTrigs))) {
+ maxPwr = c.getNetPower();
+ attackerAfterTrigs = c;
}
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
index 1a0985f9acc..ba2cb1bea5d 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
@@ -10,11 +10,10 @@ import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
import forge.card.mana.ManaCostShard;
+import forge.game.CardTraitPredicates;
import forge.game.Game;
import forge.game.GameActionUtil;
-import forge.game.ability.AbilityKey;
-import forge.game.ability.AbilityUtils;
-import forge.game.ability.ApiType;
+import forge.game.ability.*;
import forge.game.card.*;
import forge.game.combat.CombatUtil;
import forge.game.cost.*;
@@ -25,10 +24,13 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.replacement.ReplacementEffect;
+import forge.game.replacement.ReplacementLayer;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
+import forge.game.trigger.Trigger;
+import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import forge.util.TextUtil;
@@ -52,17 +54,17 @@ public class ComputerUtilMana {
public static boolean canPayManaCost(final SpellAbility sa, final Player ai, final int extraMana) {
return payManaCost(sa, ai, true, extraMana, true);
}
-
+
/**
* Return the number of colors used for payment for Converge
*/
public static int getConvergeCount(final SpellAbility sa, final Player ai) {
- ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
- if (payManaCost(cost, sa, ai, true, true)) {
- return cost.getSunburst();
- } else {
- return 0;
- }
+ ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
+ if (payManaCost(cost, sa, ai, true, true)) {
+ return cost.getSunburst();
+ } else {
+ return 0;
+ }
}
// Does not check if mana sources can be used right now, just checks for potential chance.
@@ -124,7 +126,7 @@ public class ComputerUtilMana {
for (final ManaCostShard shard : manaAbilityMap.keySet()) {
for (SpellAbility ability : manaAbilityMap.get(shard)) {
- final Card hostCard = ability.getHostCard();
+ final Card hostCard = ability.getHostCard();
if (!manaCardMap.containsKey(hostCard)) {
manaCardMap.put(hostCard, scoreManaProducingCard(hostCard));
orderedCards.add(hostCard);
@@ -238,7 +240,7 @@ public class ComputerUtilMana {
}
}
}
-
+
public static SpellAbility chooseManaAbility(ManaCostBeingPaid cost, SpellAbility sa, Player ai, ManaCostShard toPay,
Collection saList, boolean checkCosts) {
for (final SpellAbility ma : saList) {
@@ -316,7 +318,191 @@ public class ComputerUtilMana {
}
return null;
}
-
+
+ public static String predictManaReplacement(SpellAbility saPayment, Player ai, ManaCostShard toPay) {
+ Card hostCard = saPayment.getHostCard();
+ Game game = hostCard.getGame();
+ String manaProduced = toPay.isSnow() && hostCard.isSnow() ? "S" : GameActionUtil.generatedTotalMana(saPayment);
+ //String originalProduced = manaProduced;
+
+ final Map repParams = AbilityKey.newMap();
+ repParams.put(AbilityKey.Mana, manaProduced);
+ repParams.put(AbilityKey.Affected, hostCard);
+ repParams.put(AbilityKey.Player, ai);
+ repParams.put(AbilityKey.AbilityMana, saPayment); // RootAbility
+
+ // TODO Damping Sphere might replace later?
+
+ // add flags to replacementEffects to filter better?
+ List reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other);
+
+ List replaceMana = Lists.newArrayList();
+ List replaceType = Lists.newArrayList();
+ List replaceAmount = Lists.newArrayList(); // currently only multi
+
+ // try to guess the color the mana gets replaced to
+ for (ReplacementEffect re : reList) {
+ SpellAbility o = re.getOverridingAbility();
+
+ if (o == null || o.getApi() != ApiType.ReplaceMana) {
+ continue;
+ }
+
+ // this one does replace the amount too
+ if (o.hasParam("ReplaceMana")) {
+ replaceMana.add(o);
+ } else if (o.hasParam("ReplaceType") || o.hasParam("ReplaceColor")) {
+ // this one replaces the color/type
+ // check if this one can be replaced into wanted mana shard
+ replaceType.add(o);
+ } else if (o.hasParam("ReplaceAmount")) {
+ replaceAmount.add(o);
+ }
+ }
+
+ // it is better to apply these ones first
+ if (!replaceMana.isEmpty()) {
+ for (SpellAbility saMana : replaceMana) {
+ // one of then has to Any
+ // one of then has to C
+ // one of then has to B
+ String m = saMana.getParam("ReplaceMana");
+ if ("Any".equals(m)) {
+ byte rs = MagicColor.GREEN;
+ for (byte c : MagicColor.WUBRGC) {
+ if (toPay.canBePaidWithManaOfColor(c)) {
+ rs = c;
+ break;
+ }
+ }
+ manaProduced = MagicColor.toShortString(rs);
+ } else {
+ manaProduced = m;
+ }
+ }
+ }
+
+ // then apply this one
+ if (!replaceType.isEmpty()) {
+ for (SpellAbility saMana : replaceAmount) {
+ Card card = saMana.getHostCard();
+ if (saMana.hasParam("ReplaceType")) {
+ // replace color and colorless
+ String color = saMana.getParam("ReplaceType");
+ if ("Any".equals(color)) {
+ byte rs = MagicColor.GREEN;
+ for (byte c : MagicColor.WUBRGC) {
+ if (toPay.canBePaidWithManaOfColor(c)) {
+ rs = c;
+ break;
+ }
+ }
+ color = MagicColor.toShortString(rs);
+ }
+ for (byte c : MagicColor.WUBRGC) {
+ String s = MagicColor.toShortString(c);
+ manaProduced = manaProduced.replace(s, color);
+ }
+ } else if (saMana.hasParam("ReplaceColor")) {
+ // replace color
+ String color = saMana.getParam("ReplaceColor");
+ if ("Chosen".equals(color)) {
+ if (card.hasChosenColor()) {
+ color = MagicColor.toShortString(card.getChosenColor());
+ }
+ }
+ if (saMana.hasParam("ReplaceOnly")) {
+ manaProduced = manaProduced.replace(saMana.getParam("ReplaceOnly"), color);
+ } else {
+ for (byte c : MagicColor.WUBRG) {
+ String s = MagicColor.toShortString(c);
+ manaProduced = manaProduced.replace(s, color);
+ }
+ }
+ }
+ }
+ }
+
+ // then multiply if able
+ if (!replaceAmount.isEmpty()) {
+ int totalAmount = 1;
+ for (SpellAbility saMana : replaceAmount) {
+ totalAmount *= Integer.valueOf(saMana.getParam("ReplaceAmount"));
+ }
+ manaProduced = StringUtils.repeat(manaProduced, " ", totalAmount);
+ }
+
+ return manaProduced;
+ }
+
+ public static String predictManafromSpellAbility(SpellAbility saPayment, Player ai, ManaCostShard toPay) {
+ Card hostCard = saPayment.getHostCard();
+
+ String manaProduced = predictManaReplacement(saPayment, ai, toPay);
+ String originalProduced = manaProduced;
+
+ if (originalProduced.isEmpty()) {
+ return manaProduced;
+ }
+
+ // Run triggers like Nissa
+ final Map runParams = AbilityKey.mapFromCard(hostCard);
+ runParams.put(AbilityKey.Player, ai); // assuming AI would only ever gives itself mana
+ runParams.put(AbilityKey.AbilityMana, saPayment);
+ runParams.put(AbilityKey.Produced, manaProduced);
+ runParams.put(AbilityKey.Activator, ai);
+ for (Trigger tr : ai.getGame().getTriggerHandler().getActiveTrigger(TriggerType.TapsForMana, runParams)) {
+ SpellAbility trSA = tr.ensureAbility();
+ if (trSA == null) {
+ continue;
+ }
+ if (ApiType.Mana.equals(trSA.getApi())) {
+ int pAmount = trSA.hasParam("Amount") ? Integer.valueOf(trSA.getParam("Amount")) : 1;
+ String produced = trSA.getParam("Produced");
+ if (produced.equals("Chosen")) {
+ produced = MagicColor.toShortString(trSA.getHostCard().getChosenColor());
+ }
+ manaProduced += " " + StringUtils.repeat(produced, pAmount);
+ } else if (ApiType.ManaReflected.equals(trSA.getApi())) {
+ final String colorOrType = trSA.getParamOrDefault("ColorOrType", "Color");
+ // currently Color or Type, Type is colors + colorless
+ final String reflectProperty = trSA.getParam("ReflectProperty");
+
+ if (reflectProperty.equals("Produced") && !originalProduced.isEmpty()) {
+ // check if a colorless shard can be paid from the trigger
+ if (toPay.equals(ManaCostShard.COLORLESS) && colorOrType.equals("Type") && originalProduced.contains("C")) {
+ manaProduced += " " + "C";
+ } else if (originalProduced.length() == 1) {
+ // if length is only one, and it either is equal C == Type
+ if (colorOrType.equals("Type") || !originalProduced.equals("C")) {
+ manaProduced += " " + originalProduced;
+ }
+ } else {
+ // should it look for other shards too?
+ boolean found = false;
+ for (String s : originalProduced.split(" ")) {
+ if (colorOrType.equals("Type") || !s.equals("C") && toPay.canBePaidWithManaOfColor(MagicColor.fromName(s))) {
+ found = true;
+ manaProduced += " " + s;
+ break;
+ }
+ }
+ // no good mana found? just add the first generated color
+ if (!found) {
+ for (String s : originalProduced.split(" ")) {
+ if (colorOrType.equals("Type") || !s.equals("C")) {
+ manaProduced += " " + s;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return manaProduced;
+ }
+
public static CardCollection getManaSourcesToPayCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
CardCollection manaSources = new CardCollection();
@@ -392,29 +578,19 @@ public class ComputerUtilMana {
manaSources.add(saPayment.getHostCard());
setExpressColorChoice(sa, ai, cost, toPay, saPayment);
- String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
- manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
+ String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
+
//System.out.println(manaProduced);
payMultipleMana(cost, manaProduced, ai);
// remove from available lists
- /*
- * Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard()));
- * causes Android build not to compile
- * */
- Iterator itSa = sourcesForShards.values().iterator();
- while (itSa.hasNext()) {
- SpellAbility srcSa = itSa.next();
- if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
- itSa.remove();
- }
- }
+ Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
}
handleOfferingsAI(sa, true, cost.isPaid());
refundMana(manaSpentToPay, ai, sa);
-
+
return manaSources;
} // getManaSourcesToPayCost()
@@ -427,14 +603,14 @@ public class ComputerUtilMana {
List paymentList = Lists.newArrayList();
if (payManaCostFromPool(cost, sa, ai, test, manaSpentToPay)) {
- return true; // paid all from floating mana
+ return true; // paid all from floating mana
}
-
+
boolean hasConverge = sa.getHostCard().hasConverge();
ListMultimap sourcesForShards = getSourcesForShards(cost, sa, ai, test,
- checkPlayable, manaSpentToPay, hasConverge);
+ checkPlayable, manaSpentToPay, hasConverge);
if (sourcesForShards == null && !purePhyrexian) {
- return false; // no mana abilities to use for paying
+ return false; // no mana abilities to use for paying
}
final ManaPool manapool = ai.getManaPool();
@@ -443,26 +619,43 @@ public class ComputerUtilMana {
// Loop over mana needed
while (!cost.isPaid()) {
+ while (!cost.isPaid() && !manapool.isEmpty()) {
+ boolean found = false;
+ for (byte color : MagicColor.WUBRGC) {
+ if (manapool.tryPayCostWithColor(color, sa, cost)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ break;
+ }
+ }
+ if (cost.isPaid()) {
+ break;
+ }
toPay = getNextShardToPay(cost);
boolean lifeInsteadOfBlack = toPay.isBlack() && ai.hasKeyword("PayLifeInsteadOf:B");
Collection saList = null;
- if (hasConverge &&
- (toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
- final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION;
- for (final byte b : ColorSet.fromMask(unpaidColors)) { // try and pay other colors for converge
- final ManaCostShard shard = ManaCostShard.valueOf(b);
- saList = sourcesForShards.get(shard);
- if (saList != null && !saList.isEmpty()) {
- toPay = shard;
- break;
- }
- }
- if (saList == null || saList.isEmpty()) { // failed to converge, revert to paying generic
- saList = sourcesForShards.get(toPay);
- hasConverge = false;
- }
+ if (hasConverge &&
+ (toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) {
+ final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION;
+ for (final byte b : ColorSet.fromMask(unpaidColors)) {
+ // try and pay other colors for converge
+ final ManaCostShard shard = ManaCostShard.valueOf(b);
+ saList = sourcesForShards.get(shard);
+ if (saList != null && !saList.isEmpty()) {
+ toPay = shard;
+ break;
+ }
+ }
+ if (saList == null || saList.isEmpty()) {
+ // failed to converge, revert to paying generic
+ saList = sourcesForShards.get(toPay);
+ hasConverge = false;
+ }
} else {
if (!(sourcesForShards == null && purePhyrexian)) {
saList = sourcesForShards.get(toPay);
@@ -529,33 +722,23 @@ public class ComputerUtilMana {
setExpressColorChoice(sa, ai, cost, toPay, saPayment);
if (test) {
- // Check energy when testing
- CostPayEnergy energyCost = saPayment.getPayCosts().getCostEnergy();
- if (energyCost != null) {
- testEnergyPool -= Integer.parseInt(energyCost.getAmount());
- if (testEnergyPool < 0) {
- // Can't pay energy cost
- break;
- }
- }
+ // Check energy when testing
+ CostPayEnergy energyCost = saPayment.getPayCosts().getCostEnergy();
+ if (energyCost != null) {
+ testEnergyPool -= Integer.parseInt(energyCost.getAmount());
+ if (testEnergyPool < 0) {
+ // Can't pay energy cost
+ break;
+ }
+ }
- String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
- manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
- //System.out.println(manaProduced);
+ String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay);
+
+ // System.out.println(manaProduced);
payMultipleMana(cost, manaProduced, ai);
// remove from available lists
- /*
- * Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard()));
- * causes Android build not to compile
- * */
- Iterator itSa = sourcesForShards.values().iterator();
- while (itSa.hasNext()) {
- SpellAbility srcSa = itSa.next();
- if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
- itSa.remove();
- }
- }
+ Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
}
else {
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
@@ -570,20 +753,10 @@ public class ComputerUtilMana {
// no need to remove abilities from resource map,
// once their costs are paid and consume resources, they can not be used again
-
- if (hasConverge) { // hack to prevent converge re-using sources
- // remove from available lists
- /*
- * Refactoring this code to sourcesForShards.values().removeIf((SpellAbility srcSa) -> srcSa.getHostCard().equals(saPayment.getHostCard()));
- * causes Android build not to compile
- * */
- Iterator itSa = sourcesForShards.values().iterator();
- while (itSa.hasNext()) {
- SpellAbility srcSa = itSa.next();
- if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
- itSa.remove();
- }
- }
+
+ if (hasConverge) {
+ // hack to prevent converge re-using sources
+ Iterables.removeIf(sourcesForShards.values(), CardTraitPredicates.isHostCard(saPayment.getHostCard()));
}
}
}
@@ -596,15 +769,6 @@ public class ComputerUtilMana {
// extraMana, sa.getHostCard(), sa.toUnsuppressedString(), StringUtils.join(paymentPlan, "\n\t"));
// }
- // See if it's possible to pay with something that was left in the mana pool in corner cases,
- // e.g. Gemstone Caverns with a Luck counter on it generating colored mana (which fails to be
- // processed correctly on a per-ability basis, leaving floating mana in the pool)
- if (!cost.isPaid() && !manapool.isEmpty()) {
- for (byte color : MagicColor.WUBRGC) {
- manapool.tryPayCostWithColor(color, sa, cost);
- }
- }
-
// The cost is still unpaid, so refund the mana and report
if (!cost.isPaid()) {
refundMana(manaSpentToPay, ai, sa);
@@ -633,15 +797,16 @@ public class ComputerUtilMana {
}
- /**
- * Creates a mapping between the required mana shards and the available spell abilities to pay for them
- */
- private static ListMultimap getSourcesForShards(final ManaCostBeingPaid cost,
- final SpellAbility sa, final Player ai, final boolean test, final boolean checkPlayable,
- List manaSpentToPay, final boolean hasConverge) {
- // arrange all mana abilities by color produced.
+ /**
+ * Creates a mapping between the required mana shards and the available spell abilities to pay for them
+ */
+ private static ListMultimap getSourcesForShards(final ManaCostBeingPaid cost,
+ final SpellAbility sa, final Player ai, final boolean test, final boolean checkPlayable,
+ List manaSpentToPay, final boolean hasConverge) {
+ // arrange all mana abilities by color produced.
final ListMultimap manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, checkPlayable);
- if (manaAbilityMap.isEmpty()) { // no mana abilities, bailing out
+ if (manaAbilityMap.isEmpty()) {
+ // no mana abilities, bailing out
refundMana(manaSpentToPay, ai, sa);
handleOfferingsAI(sa, test, cost.isPaid());
return null;
@@ -652,25 +817,26 @@ public class ComputerUtilMana {
// select which abilities may be used for each shard
ListMultimap sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
- if (hasConverge) { // add extra colors for paying converge
- final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION;
- for (final byte b : ColorSet.fromMask(unpaidColors)) {
- final ManaCostShard shard = ManaCostShard.valueOf(b);
- if (!sourcesForShards.containsKey(shard)) {
- if (ai.getManaPool().canPayForShardWithColor(shard, b)) {
+ if (hasConverge) {
+ // add extra colors for paying converge
+ final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION;
+ for (final byte b : ColorSet.fromMask(unpaidColors)) {
+ final ManaCostShard shard = ManaCostShard.valueOf(b);
+ if (!sourcesForShards.containsKey(shard)) {
+ if (ai.getManaPool().canPayForShardWithColor(shard, b)) {
for (SpellAbility saMana : manaAbilityMap.get((int)b)) {
- sourcesForShards.get(shard).add(sourcesForShards.get(shard).size(), saMana);
+ sourcesForShards.get(shard).add(saMana);
}
}
- }
- }
+ }
+ }
}
sortManaAbilities(sourcesForShards, sa);
if (DEBUG_MANA_PAYMENT) {
System.out.println("DEBUG_MANA_PAYMENT: sourcesForShards = " + sourcesForShards);
}
- return sourcesForShards;
- }
+ return sourcesForShards;
+ }
/**
* Checks if the given mana cost can be paid from floating mana.
@@ -681,9 +847,9 @@ public class ComputerUtilMana {
* @param manaSpentToPay list of mana spent
* @return whether the floating mana is sufficient to pay the cost fully
*/
- private static boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai,
+ private static boolean payManaCostFromPool(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai,
final boolean test, List manaSpentToPay) {
- final boolean hasConverge = sa.getHostCard().hasConverge();
+ final boolean hasConverge = sa.getHostCard().hasConverge();
List unpaidShards = cost.getUnpaidShards();
Collections.sort(unpaidShards); // most difficult shards must come first
for (ManaCostShard part : unpaidShards) {
@@ -764,15 +930,15 @@ public class ComputerUtilMana {
// if we are simulating mana payment for the human controller, use the first mana available (and avoid prompting the human player)
if (!(ai.getController() instanceof PlayerControllerAi)) {
- return manaChoices.get(0);
+ return manaChoices.get(0);
}
// Let them choose then
return ai.getController().chooseManaFromPool(manaChoices);
}
- private static List> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
- final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) {
+ private static List> selectManaToPayFor(final ManaPool manapool, final ManaCostShard shard,
+ final SpellAbility saBeingPaidFor, String restriction, final byte colorsPaid) {
final List> weightedOptions = new ArrayList<>();
for (final Mana thisMana : manapool) {
if (!manapool.canPayForShardWithColor(shard, thisMana.getColor())) {
@@ -794,11 +960,11 @@ public class ComputerUtilMana {
int weight = 0;
if (colorsPaid == -1) {
- // prefer colorless mana to spend
- weight += thisMana.isColorless() ? 5 : 0;
+ // prefer colorless mana to spend
+ weight += thisMana.isColorless() ? 5 : 0;
} else {
- // get more colors for converge
- weight += (thisMana.getColor() | colorsPaid) != colorsPaid ? 5 : 0;
+ // get more colors for converge
+ weight += (thisMana.getColor() | colorsPaid) != colorsPaid ? 5 : 0;
}
// prefer restricted mana to spend
@@ -815,7 +981,7 @@ public class ComputerUtilMana {
}
return weightedOptions;
}
-
+
private static void setExpressColorChoice(final SpellAbility sa, final Player ai, ManaCostBeingPaid cost,
ManaCostShard toPay, SpellAbility saPayment) {
@@ -856,7 +1022,7 @@ public class ComputerUtilMana {
if (isManaSourceReserved(ai, sourceCard, sa)) {
return false;
}
-
+
if (toPay.isSnow() && !sourceCard.isSnow()) {
return false;
}
@@ -1002,7 +1168,7 @@ public class ComputerUtilMana {
*
* getComboManaChoice.
*
- *
+ *
* @param manaAb
* a {@link forge.game.spellability.SpellAbility} object.
* @param saRoot
@@ -1026,7 +1192,7 @@ public class ComputerUtilMana {
choice = abMana.getExpressChoice();
abMana.clearExpressChoice();
byte colorMask = ManaAtom.fromName(choice);
- if (abMana.canProduce(choice, manaAb) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) {
+ if (manaAb.canProduce(choice) && testCost.isAnyPartPayableWith(colorMask, ai.getManaPool())) {
choiceString.append(choice);
payMultipleMana(testCost, choice, ai);
continue;
@@ -1103,7 +1269,7 @@ public class ComputerUtilMana {
}
return unused.isEmpty() ? null : StringUtils.join(unused, ' ');
}
-
+
/**
* Find all mana sources.
* @param manaAbilityMap The map of SpellAbilities that produce mana.
@@ -1135,7 +1301,7 @@ public class ComputerUtilMana {
res.putAll(shard, manaAbilityMap.get(ManaAtom.GENERIC));
continue;
}
-
+
if (shard == ManaCostShard.GENERIC) {
continue;
}
@@ -1163,7 +1329,7 @@ public class ComputerUtilMana {
* @return ManaCost
*/
public static ManaCostBeingPaid calculateManaCost(final SpellAbility sa, final boolean test, final int extraMana) {
- Card card = sa.getHostCard();
+ Card card = sa.getHostCard();
ZoneType castFromBackup = null;
if (test && sa.isSpell()) {
castFromBackup = card.getCastFrom();
@@ -1196,14 +1362,14 @@ public class ComputerUtilMana {
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
}
}
-
+
CostAdjustment.adjust(cost, sa, null, test);
int timesMultikicked = card.getKickerMagnitude();
if (timesMultikicked > 0 && sa.hasParam("Announce") && sa.getParam("Announce").startsWith("Multikicker")) {
ManaCost mkCost = sa.getMultiKickerManaCost();
for (int i = 0; i < timesMultikicked; i++) {
- cost.addManaCost(mkCost);
+ cost.addManaCost(mkCost);
}
sa.setSVar("Multikicker", String.valueOf(timesMultikicked));
}
@@ -1387,20 +1553,6 @@ public class ComputerUtilMana {
final ListMultimap manaMap = ArrayListMultimap.create();
final Game game = ai.getGame();
- List replacementEffects = new ArrayList<>();
- for (final Player p : game.getPlayers()) {
- for (final Card crd : p.getAllCards()) {
- for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
- if (replacementEffect.requirementsCheck(game)
- && replacementEffect.getMode() == ReplacementType.ProduceMana
- && replacementEffect.hasParam("ManaReplacement")
- && replacementEffect.zonesCheck(game.getZoneOf(crd))) {
- replacementEffects.add(replacementEffect);
- }
- }
- }
- }
-
// Loop over all current available mana sources
for (final Card sourceCard : getAvailableManaSources(ai, checkPlayable)) {
if (DEBUG_MANA_PAYMENT) {
@@ -1430,48 +1582,80 @@ public class ComputerUtilMana {
}
manaMap.get(ManaAtom.GENERIC).add(m); // add to generic source list
- AbilityManaPart mp = m.getManaPart();
- // setup produce mana replacement effects
- final Map repParams = AbilityKey.newMap();
- repParams.put(AbilityKey.Mana, mp.getOrigProduced());
- repParams.put(AbilityKey.Affected, sourceCard);
- repParams.put(AbilityKey.Player, ai);
- repParams.put(AbilityKey.AbilityMana, m);
+ SpellAbility tail = m;
+ while (tail != null) {
+ AbilityManaPart mp = m.getManaPart();
+ if (mp != null && tail.metConditions()) {
+ // TODO Replacement Check currently doesn't work for reflected colors
- for (final ReplacementEffect replacementEffect : replacementEffects) {
- if (replacementEffect.canReplace(repParams)) {
- Card crd = replacementEffect.getHostCard();
- String repType = crd.getSVar(replacementEffect.getParam("ManaReplacement"));
- if (repType.contains("Chosen")) {
- repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(crd.getChosenColor()));
+ // setup produce mana replacement effects
+ String origin = mp.getOrigProduced();
+ final Map repParams = AbilityKey.newMap();
+ repParams.put(AbilityKey.Mana, origin);
+ repParams.put(AbilityKey.Affected, sourceCard);
+ repParams.put(AbilityKey.Player, ai);
+ repParams.put(AbilityKey.AbilityMana, m); // RootAbility
+
+ List reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other);
+
+ if (reList.isEmpty()) {
+ Set reflectedColors = CardUtil.getReflectableManaColors(m);
+ // find possible colors
+ for (byte color : MagicColor.WUBRG) {
+ if (tail.canThisProduce(MagicColor.toShortString(color)) || reflectedColors.contains(MagicColor.toLongString(color))) {
+ manaMap.put((int)color, m);
+ }
+ }
+ if (m.canThisProduce("C") || reflectedColors.contains(MagicColor.Constant.COLORLESS)) {
+ manaMap.put(ManaAtom.COLORLESS, m);
+ }
+ } else {
+ // try to guess the color the mana gets replaced to
+ for (ReplacementEffect re : reList) {
+ SpellAbility o = re.getOverridingAbility();
+ String replaced = origin;
+ if (o == null || o.getApi() != ApiType.ReplaceMana) {
+ continue;
+ }
+ if (o.hasParam("ReplaceMana")) {
+ replaced = o.getParam("ReplaceMana");
+ } else if (o.hasParam("ReplaceType")) {
+ String color = o.getParam("ReplaceType");
+ for (byte c : MagicColor.WUBRGC) {
+ String s = MagicColor.toShortString(c);
+ replaced = replaced.replace(s, color);
+ }
+ } else if (o.hasParam("ReplaceColor")) {
+ String color = o.getParam("ReplaceColor");
+ if (o.hasParam("ReplaceOnly")) {
+ replaced = replaced.replace(o.getParam("ReplaceOnly"), color);
+ } else {
+ for (byte c : MagicColor.WUBRG) {
+ String s = MagicColor.toShortString(c);
+ replaced = replaced.replace(s, color);
+ }
+ }
+ }
+
+ for (byte color : MagicColor.WUBRG) {
+ if ("Any".equals(replaced) || replaced.contains(MagicColor.toShortString(color))) {
+ manaMap.put((int)color, m);
+ }
+ }
+
+ if (replaced.contains("C")) {
+ manaMap.put(ManaAtom.COLORLESS, m);
+ }
+
+ }
}
- mp.setManaReplaceType(repType);
}
+ tail = tail.getSubAbility();
}
- Set reflectedColors = CardUtil.getReflectableManaColors(m);
- // find possible colors
- if (mp.canProduce("W", m) || reflectedColors.contains(MagicColor.Constant.WHITE)) {
- manaMap.get(ManaAtom.WHITE).add(m);
- }
- if (mp.canProduce("U", m) || reflectedColors.contains(MagicColor.Constant.BLUE)) {
- manaMap.get(ManaAtom.BLUE).add(m);
- }
- if (mp.canProduce("B", m) || reflectedColors.contains(MagicColor.Constant.BLACK)) {
- manaMap.get(ManaAtom.BLACK).add(m);
- }
- if (mp.canProduce("R", m) || reflectedColors.contains(MagicColor.Constant.RED)) {
- manaMap.get(ManaAtom.RED).add(m);
- }
- if (mp.canProduce("G", m) || reflectedColors.contains(MagicColor.Constant.GREEN)) {
- manaMap.get(ManaAtom.GREEN).add(m);
- }
- if (mp.canProduce("C", m) || reflectedColors.contains(MagicColor.Constant.COLORLESS)) {
- manaMap.get(ManaAtom.COLORLESS).add(m);
- }
- if (mp.isSnow()) {
- manaMap.get(ManaAtom.IS_SNOW).add(m);
+ if (m.getHostCard().isSnow()) {
+ manaMap.put(ManaAtom.IS_SNOW, m);
}
if (DEBUG_MANA_PAYMENT) {
System.out.println("DEBUG_MANA_PAYMENT: groupSourcesByManaColor manaMap = " + manaMap);
@@ -1486,7 +1670,7 @@ public class ComputerUtilMana {
*
* determineLeftoverMana.
*
- *
+ *
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param player
@@ -1507,7 +1691,7 @@ public class ComputerUtilMana {
*
* determineLeftoverMana.
*
- *
+ *
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param player
@@ -1536,7 +1720,7 @@ public class ComputerUtilMana {
*
* getAIPlayableMana.
*
- *
+ *
* @return a {@link java.util.List} object.
*/
public static List getAIPlayableMana(Card c) {
@@ -1583,8 +1767,8 @@ public class ComputerUtilMana {
sa.resetSacrificedAsEmerge();
}
}
-
-
+
+
/**
* Matches list of creatures to shards in mana cost for convoking.
* @param cost cost of convoked ability
diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
index a96ee50cd61..ab50c1a806f 100644
--- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
+++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
@@ -494,7 +494,7 @@ public class PlayerControllerAi extends PlayerController {
}
@Override
- public TargetChoices chooseNewTargetsFor(SpellAbility ability) {
+ public TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate filter, boolean optional) {
// AI currently can't do this. But when it can it will need to be based on Ability API
return null;
}
@@ -833,6 +833,9 @@ public class PlayerControllerAi extends PlayerController {
@Override
public byte chooseColor(String message, SpellAbility sa, ColorSet colors) {
+ if (colors.countColors() < 2) {
+ return Iterables.getFirst(colors, MagicColor.WHITE);
+ }
// You may switch on sa.getApi() here and use sa.getParam("AILogic")
CardCollectionView hand = player.getCardsIn(ZoneType.Hand);
if (sa.getApi() == ApiType.Mana) {
@@ -978,9 +981,13 @@ public class PlayerControllerAi extends PlayerController {
if (sa.isSpell()) {
player.getGame().getStackZone().add(sa.getHostCard());
}
- // TODO check if static abilities needs to be run for things affecting the copy?
+
+ /* FIXME: the new implementation (below) requires implementing setupNewTargets in the AI controller, among other possible changes, otherwise breaks AI
+ if (sa.isMayChooseNewTargets()) {
+ sa.setupNewTargets(player);
+ }
+ */
if (sa.isMayChooseNewTargets() && !sa.setupTargets()) {
- // if targets can't be done, remove copy from existence
if (sa.isSpell()) {
sa.getHostCard().ceaseToExist();
}
diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java
index 2173f53bb8f..ecbd71f2fe4 100644
--- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java
+++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java
@@ -26,7 +26,6 @@ import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GameType;
-import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
@@ -636,10 +635,7 @@ public class SpecialCardAi {
boolean canRetFromGrave = false;
String name = c.getName().replace(',', ';');
for (Trigger t : c.getTriggers()) {
- SpellAbility ab = null;
- if (t.hasParam("Execute")) {
- ab = AbilityFactory.getAbility(c.getSVar(t.getParam("Execute")), c);
- }
+ SpellAbility ab = t.ensureAbility();
if (ab == null) { continue; }
if (ab.getApi() == ApiType.ChangeZone
diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java
index fa33c11087a..d8c185684fd 100644
--- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java
+++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java
@@ -111,7 +111,7 @@ public abstract class SpellAbilityAi {
if (aiLogic.equals("CheckCondition")) {
SpellAbility saCopy = sa.copy();
saCopy.setActivatingPlayer(ai);
- return saCopy.getConditions().areMet(saCopy);
+ return saCopy.metConditions();
}
return !("Never".equals(aiLogic));
diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
index 7fcc06b0507..d7b12a02bcd 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java
@@ -122,7 +122,7 @@ public class AnimateAi extends SpellAbilityAi {
final Card source = sa.getHostCard();
final Game game = aiPlayer.getGame();
final PhaseHandler ph = game.getPhaseHandler();
- if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) {
+ if (!sa.metConditions() && sa.getSubAbility() == null) {
return false; // what is this for?
}
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getApi() == ApiType.Sacrifice) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
index b3ae3638b6f..ed7d26cc043 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
@@ -1086,12 +1086,7 @@ public class AttachAi extends SpellAbilityAi {
final Map params = t.getMapParams();
if ("Card.Self".equals(params.get("ValidCard"))
&& "Battlefield".equals(params.get("Destination"))) {
- SpellAbility trigSa = null;
- 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();
- }
+ SpellAbility trigSa = t.ensureAbility();
if (trigSa != null && trigSa.getApi() == ApiType.DealDamage && "Enchanted".equals(trigSa.getParam("Defined"))) {
for (Card target : list) {
if (!target.getController().isOpponentOf(ai)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
index 74c5c0aa1ab..c364996f774 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
@@ -304,10 +304,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
// don't play if the conditions aren't met, unless it would trigger a beneficial sub-condition
- if (!activateForCost && !sa.getConditions().areMet(sa)) {
+ if (!activateForCost && !sa.metConditions()) {
final AbilitySub abSub = sa.getSubAbility();
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
- if (!abSub.getConditions().areMet(abSub)) {
+ if (!abSub.metConditions()) {
return false;
}
} else {
@@ -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
// return the commander to the Command zone.
if (subApi == ApiType.DelayedTrigger) {
- SpellAbility exec = causeSub.getAdditionalAbility("Execute");
+ SpellAbility exec = causeSub.getAdditionalAbility("Execute");
if (exec != null && exec.getApi() == ApiType.ChangeZone) {
// A blink effect implemented using a delayed trigger
return !"Exile".equals(exec.getParam("Origin")) || !"Battlefield".equals(exec.getParam("Destination"));
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
index dee21c7340b..162b90f82f8 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
@@ -126,7 +126,7 @@ public class CountersPutAi extends SpellAbilityAi {
Card choice = null;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
- final boolean divided = sa.hasParam("DividedAsYouChoose");
+ final boolean divided = sa.isDividedAsYouChoose();
final String logic = sa.getParamOrDefault("AILogic", "");
PhaseHandler ph = ai.getGame().getPhaseHandler();
@@ -290,7 +290,7 @@ public class CountersPutAi extends SpellAbilityAi {
return doMoveCounterLogic(ai, sa, ph);
}
- if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) {
+ if (!sa.metConditions() && sa.getSubAbility() == null) {
return false;
}
@@ -398,16 +398,15 @@ public class CountersPutAi extends SpellAbilityAi {
}
if (!ai.getGame().getStack().isEmpty() && !SpellAbilityAi.isSorcerySpeed(sa)) {
- final TargetRestrictions abTgt = sa.getTargetRestrictions();
// only evaluates case where all tokens are placed on a single target
- if (sa.usesTargeting() && abTgt.getMinTargets(source, sa) < 2) {
+ if (sa.usesTargeting() && sa.getMinTargets() < 2) {
if (ComputerUtilCard.canPumpAgainstRemoval(ai, sa)) {
Card c = sa.getTargets().getFirstTargetedCard();
if (sa.getTargets().size() > 1) {
sa.resetTargets();
sa.getTargets().add(c);
}
- abTgt.addDividedAllocation(sa.getTargetCard(), amount);
+ sa.addDividedAllocation(sa.getTargetCard(), amount);
return true;
} else {
return false;
@@ -462,7 +461,6 @@ public class CountersPutAi extends SpellAbilityAi {
}
if (sourceName.equals("Abzan Charm")) {
- final TargetRestrictions abTgt = sa.getTargetRestrictions();
// specific AI for instant with distribute two +1/+1 counters
ComputerUtilCard.sortByEvaluateCreature(list);
// maximise the number of targets
@@ -472,11 +470,11 @@ public class CountersPutAi extends SpellAbilityAi {
if (ComputerUtilCard.shouldPumpCard(ai, sa, c, i, i,
Lists.newArrayList())) {
sa.getTargets().add(c);
- abTgt.addDividedAllocation(c, i);
+ sa.addDividedAllocation(c, i);
left -= i;
}
- if (left < i || sa.getTargets().size() == abTgt.getMaxTargets(source, sa)) {
- abTgt.addDividedAllocation(sa.getTargets().getFirstTargetedCard(), left + i);
+ if (left < i || sa.getTargets().size() == sa.getMaxTargets()) {
+ sa.addDividedAllocation(sa.getTargets().getFirstTargetedCard(), left + i);
left = 0;
break;
}
@@ -542,7 +540,7 @@ public class CountersPutAi extends SpellAbilityAi {
list.remove(choice);
sa.getTargets().add(choice);
if (divided) {
- sa.getTargetRestrictions().addDividedAllocation(choice, amount);
+ sa.addDividedAllocation(choice, amount);
break;
}
choice = null;
@@ -609,7 +607,7 @@ public class CountersPutAi extends SpellAbilityAi {
final String logic = sa.getParamOrDefault("AILogic", "");
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
- final boolean divided = sa.hasParam("DividedAsYouChoose");
+ final boolean divided = sa.isDividedAsYouChoose();
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
@@ -672,7 +670,7 @@ public class CountersPutAi extends SpellAbilityAi {
list.remove(choice);
sa.getTargets().add(choice);
if (divided) {
- sa.getTargetRestrictions().addDividedAllocation(choice, amount);
+ sa.addDividedAllocation(choice, amount);
break;
}
}
@@ -690,7 +688,7 @@ public class CountersPutAi extends SpellAbilityAi {
CardCollection list;
final String type = sa.getParam("CounterType");
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
- final boolean divided = sa.hasParam("DividedAsYouChoose");
+ final boolean divided = sa.isDividedAsYouChoose();
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
int left = amount;
@@ -818,12 +816,11 @@ public class CountersPutAi extends SpellAbilityAi {
}
}
if (choice != null && divided) {
- final TargetRestrictions abTgt = sa.getTargetRestrictions();
int alloc = Math.max(amount / totalTargets, 1);
- if (sa.getTargets().size() == Math.min(totalTargets, abTgt.getMaxTargets(sa.getHostCard(), sa)) - 1) {
- abTgt.addDividedAllocation(choice, left);
+ if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) {
+ sa.addDividedAllocation(choice, left);
} else {
- abTgt.addDividedAllocation(choice, alloc);
+ sa.addDividedAllocation(choice, alloc);
left -= alloc;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
index 35a2ba27f47..471cf90696f 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
@@ -268,7 +268,7 @@ public class DamageDealAi extends DamageAiBase {
if ((damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) ||
sourceName.equals("Crater's Claws")){
// If I can kill my target by paying less mana, do it
- if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.hasParam("DividedAsYouChoose")) {
+ if (sa.usesTargeting() && !sa.getTargets().isTargetingAnyPlayer() && !sa.isDividedAsYouChoose()) {
int actualPay = dmg;
final boolean noPrevention = sa.hasParam("NoPrevention");
for (final Card c : sa.getTargets().getTargetCards()) {
@@ -547,7 +547,7 @@ public class DamageDealAi extends DamageAiBase {
final boolean noPrevention = sa.hasParam("NoPrevention");
final Game game = source.getGame();
final PhaseHandler phase = game.getPhaseHandler();
- final boolean divided = sa.hasParam("DividedAsYouChoose");
+ final boolean divided = sa.isDividedAsYouChoose();
final boolean oppTargetsChoice = sa.hasParam("TargetingPlayer");
final String logic = sa.getParamOrDefault("AILogic", "");
@@ -613,7 +613,7 @@ public class DamageDealAi extends DamageAiBase {
if (assignedDamage <= dmg
&& humanCreature.getShieldCount() == 0 && !ComputerUtil.canRegenerate(humanCreature.getController(), humanCreature)) {
tcs.add(humanCreature);
- tgt.addDividedAllocation(humanCreature, assignedDamage);
+ sa.addDividedAllocation(humanCreature, assignedDamage);
lastTgt = humanCreature;
dmg -= assignedDamage;
}
@@ -625,7 +625,7 @@ public class DamageDealAi extends DamageAiBase {
}
}
if (dmg > 0 && lastTgt != null) {
- tgt.addDividedAllocation(lastTgt, tgt.getDividedValue(lastTgt) + dmg);
+ sa.addDividedAllocation(lastTgt, sa.getDividedValue(lastTgt) + dmg);
dmg = 0;
return true;
}
@@ -635,14 +635,14 @@ public class DamageDealAi extends DamageAiBase {
continue;
}
tcs.add(humanCreature);
- tgt.addDividedAllocation(humanCreature, dmg);
+ sa.addDividedAllocation(humanCreature, dmg);
dmg = 0;
return true;
}
}
int totalTargetedSoFar = -1;
- while (tcs.size() < tgt.getMaxTargets(source, sa)) {
+ while (sa.canAddMoreTarget()) {
if (totalTargetedSoFar == tcs.size()) {
// Avoid looping endlessly when choosing targets for cards with variable target number and type
// like Jaya's Immolating Inferno
@@ -664,7 +664,7 @@ public class DamageDealAi extends DamageAiBase {
if (divided) {
int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
assignedDamage = Math.min(dmg, assignedDamage);
- tgt.addDividedAllocation(c, assignedDamage);
+ sa.addDividedAllocation(c, assignedDamage);
dmg = dmg - assignedDamage;
if (dmg <= 0) {
break;
@@ -680,7 +680,7 @@ public class DamageDealAi extends DamageAiBase {
if (this.shouldTgtP(ai, sa, dmg, noPrevention)) {
tcs.add(enemy);
if (divided) {
- tgt.addDividedAllocation(enemy, dmg);
+ sa.addDividedAllocation(enemy, dmg);
break;
}
continue;
@@ -702,7 +702,7 @@ public class DamageDealAi extends DamageAiBase {
if (divided) {
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (assignedDamage <= dmg) {
- tgt.addDividedAllocation(c, assignedDamage);
+ sa.addDividedAllocation(c, assignedDamage);
}
dmg = dmg - assignedDamage;
if (dmg <= 0) {
@@ -739,7 +739,7 @@ public class DamageDealAi extends DamageAiBase {
if (freePing && sa.canTarget(enemy) && (!avoidTargetP(ai, sa))) {
tcs.add(enemy);
if (divided) {
- tgt.addDividedAllocation(enemy, dmg);
+ sa.addDividedAllocation(enemy, dmg);
break;
}
}
@@ -757,9 +757,9 @@ public class DamageDealAi extends DamageAiBase {
if (divided) {
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention);
if (assignedDamage <= dmg) {
- tgt.addDividedAllocation(c, assignedDamage);
+ sa.addDividedAllocation(c, assignedDamage);
} else {
- tgt.addDividedAllocation(c, dmg);
+ sa.addDividedAllocation(c, dmg);
}
dmg = dmg - assignedDamage;
if (dmg <= 0) {
@@ -785,7 +785,7 @@ public class DamageDealAi extends DamageAiBase {
(!avoidTargetP(ai, sa))) {
tcs.add(enemy);
if (divided) {
- tgt.addDividedAllocation(enemy, dmg);
+ sa.addDividedAllocation(enemy, dmg);
break;
}
continue;
@@ -880,16 +880,16 @@ public class DamageDealAi extends DamageAiBase {
private boolean damageChooseRequiredTargets(final Player ai, final SpellAbility sa, final TargetRestrictions tgt, final int dmg) {
// this is for Triggered targets that are mandatory
final boolean noPrevention = sa.hasParam("NoPrevention");
- final boolean divided = sa.hasParam("DividedAsYouChoose");
+ final boolean divided = sa.isDividedAsYouChoose();
final Player opp = ai.getWeakestOpponent();
- while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
+ while (sa.canAddMoreTarget()) {
if (tgt.canTgtPlaneswalker()) {
final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, true);
if (c != null) {
sa.getTargets().add(c);
if (divided) {
- tgt.addDividedAllocation(c, dmg);
+ sa.addDividedAllocation(c, dmg);
break;
}
continue;
@@ -902,7 +902,7 @@ public class DamageDealAi extends DamageAiBase {
if (c != null) {
sa.getTargets().add(c);
if (divided) {
- tgt.addDividedAllocation(c, dmg);
+ sa.addDividedAllocation(c, dmg);
break;
}
continue;
@@ -912,7 +912,7 @@ public class DamageDealAi extends DamageAiBase {
if (sa.canTarget(opp)) {
if (sa.getTargets().add(opp)) {
if (divided) {
- tgt.addDividedAllocation(opp, dmg);
+ sa.addDividedAllocation(opp, dmg);
break;
}
continue;
@@ -927,7 +927,7 @@ public class DamageDealAi extends DamageAiBase {
Card c = ComputerUtilCard.getWorstPermanentAI(indestructible, false, false, false, false);
sa.getTargets().add(c);
if (divided) {
- tgt.addDividedAllocation(c, dmg);
+ sa.addDividedAllocation(c, dmg);
break;
}
continue;
@@ -938,7 +938,7 @@ public class DamageDealAi extends DamageAiBase {
if (c != null) {
sa.getTargets().add(c);
if (divided) {
- tgt.addDividedAllocation(c, dmg);
+ sa.addDividedAllocation(c, dmg);
break;
}
continue;
@@ -948,7 +948,7 @@ public class DamageDealAi extends DamageAiBase {
if (sa.canTarget(ai)) {
if (sa.getTargets().add(ai)) {
if (divided) {
- tgt.addDividedAllocation(ai, dmg);
+ sa.addDividedAllocation(ai, dmg);
break;
}
continue;
@@ -988,7 +988,7 @@ public class DamageDealAi extends DamageAiBase {
return false;
}
- if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.hasParam("DividedAsYouChoose")) {
+ if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid") && !sa.isDividedAsYouChoose()) {
// If I can kill my target by paying less mana, do it
int actualPay = 0;
final boolean noPrevention = sa.hasParam("NoPrevention");
diff --git a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java
index 6e5259f25cb..964c427c649 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamagePreventAi.java
@@ -146,8 +146,8 @@ public class DamagePreventAi extends SpellAbilityAi {
}
}
}
- if (tgt != null && sa.hasParam("DividedAsYouChoose") && sa.getTargets() != null && !sa.getTargets().isEmpty()) {
- tgt.addDividedAllocation(sa.getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
+ if (sa.usesTargeting() && sa.isDividedAsYouChoose() && !sa.getTargets().isEmpty()) {
+ sa.addDividedAllocation(sa.getTargets().get(0), AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
}
return chance;
@@ -179,12 +179,11 @@ public class DamagePreventAi extends SpellAbilityAi {
* @return a boolean.
*/
private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
- final TargetRestrictions tgt = sa.getTargetRestrictions();
sa.resetTargets();
// filter AIs battlefield by what I can target
final Game game = ai.getGame();
CardCollectionView targetables = game.getCardsIn(ZoneType.Battlefield);
- targetables = CardLists.getValidCards(targetables, tgt.getValidTgts(), ai, sa.getHostCard(), sa);
+ targetables = CardLists.getTargetableCards(targetables, sa);
final List compTargetables = CardLists.filterControlledBy(targetables, ai);
Card target = null;
@@ -215,8 +214,8 @@ public class DamagePreventAi extends SpellAbilityAi {
target = ComputerUtilCard.getCheapestPermanentAI(targetables, sa, true);
}
sa.getTargets().add(target);
- if (sa.hasParam("DividedAsYouChoose")) {
- tgt.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
+ if (sa.isDividedAsYouChoose()) {
+ sa.addDividedAllocation(target, AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa));
}
return true;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java b/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java
index bd22d902785..bede875289d 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DelayedTriggerAi.java
@@ -3,7 +3,6 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import forge.ai.*;
import forge.card.mana.ManaCost;
-import forge.game.ability.AbilityFactory;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardLists;
@@ -22,11 +21,9 @@ public class DelayedTriggerAi extends SpellAbilityAi {
// TODO: improve ai
return true;
}
- SpellAbility trigsa = null;
- if (sa.hasAdditionalAbility("Execute")) {
- trigsa = sa.getAdditionalAbility("Execute");
- } else {
- trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
+ SpellAbility trigsa = sa.getAdditionalAbility("Execute");
+ if (trigsa == null) {
+ return false;
}
trigsa.setActivatingPlayer(ai);
@@ -39,12 +36,11 @@ public class DelayedTriggerAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
- SpellAbility trigsa = null;
- if (sa.hasAdditionalAbility("Execute")) {
- trigsa = sa.getAdditionalAbility("Execute");
- } else {
- trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
+ SpellAbility trigsa = sa.getAdditionalAbility("Execute");
+ if (trigsa == null) {
+ return false;
}
+
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai);
@@ -143,11 +139,9 @@ public class DelayedTriggerAi extends SpellAbilityAi {
}
// Generic logic
- SpellAbility trigsa = null;
- if (sa.hasAdditionalAbility("Execute")) {
- trigsa = sa.getAdditionalAbility("Execute");
- } else {
- trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
+ SpellAbility trigsa = sa.getAdditionalAbility("Execute");
+ if (trigsa == null) {
+ return false;
}
trigsa.setActivatingPlayer(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
diff --git a/forge-ai/src/main/java/forge/ai/ability/FightAi.java b/forge-ai/src/main/java/forge/ai/ability/FightAi.java
index 1b95b09c1ca..714376bb3a8 100644
--- a/forge-ai/src/main/java/forge/ai/ability/FightAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/FightAi.java
@@ -3,6 +3,7 @@ package forge.ai.ability;
import forge.ai.*;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
+import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
@@ -193,7 +194,7 @@ public class FightAi extends SpellAbilityAi {
for (Card humanCreature : humCreatures) {
for (Card aiCreature : aiCreatures) {
if (source.isSpell()) { // heroic triggers adding counters and prowess
- final int bonus = getSpellBonus(aiCreature);
+ final int bonus = getSpellBonus(aiCreature);
power += bonus;
toughness += bonus;
}
@@ -247,28 +248,32 @@ public class FightAi extends SpellAbilityAi {
return false;
}
- /**
- * Compute the bonus from Heroic +1/+1 counters or Prowess
- */
- private static int getSpellBonus(final Card aiCreature) {
- for (Trigger t : aiCreature.getTriggers()) {
- if (t.getMode() == TriggerType.SpellCast) {
- final Map params = t.getMapParams();
- if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))
- && params.containsKey("Execute")) {
- SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
- if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
- return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
- }
- break;
- }
- if ("ProwessPump".equals(params.get("Execute"))) {
- return 1;
- }
- }
- }
- return 0;
- }
+ /**
+ * Compute the bonus from Heroic +1/+1 counters or Prowess
+ */
+ private static int getSpellBonus(final Card aiCreature) {
+ for (Trigger t : aiCreature.getTriggers()) {
+ if (t.getMode() == TriggerType.SpellCast) {
+ SpellAbility sa = t.ensureAbility();
+ final Map params = t.getMapParams();
+ if (sa == null) {
+ continue;
+ }
+ if (ApiType.PutCounter.equals(sa.getApi())) {
+ if ("Card.Self".equals(params.get("TargetsValid")) && "You".equals(params.get("ValidActivatingPlayer"))) {
+ SpellAbility heroic = AbilityFactory.getAbility(aiCreature.getSVar(params.get("Execute")),aiCreature);
+ if ("Self".equals(heroic.getParam("Defined")) && "P1P1".equals(heroic.getParam("CounterType"))) {
+ return AbilityUtils.calculateAmount(aiCreature, heroic.getParam("CounterNum"), heroic);
+ }
+ break;
+ }
+ } else if (ApiType.Pump.equals(sa.getApi())) {
+ // TODO add prowess boost
+ }
+ }
+ }
+ return 0;
+ }
private static boolean shouldFight(Card fighter, Card opponent, int pumpAttack, int pumpDefense) {
if (canKill(fighter, opponent, pumpAttack)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java b/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java
index 2619018d4bc..b241dd8f7bf 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java
@@ -1,7 +1,6 @@
package forge.ai.ability;
import forge.ai.*;
-import forge.game.ability.AbilityFactory;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
@@ -16,12 +15,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
return true;
}
- SpellAbility trigsa = null;
- if (sa.hasAdditionalAbility("Execute")) {
- trigsa = sa.getAdditionalAbility("Execute");
- } else {
- trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
+ SpellAbility trigsa = sa.getAdditionalAbility("Execute");
+ if (trigsa == null) {
+ return false;
}
+
trigsa.setActivatingPlayer(ai);
if (trigsa instanceof AbilitySub) {
@@ -33,12 +31,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
- SpellAbility trigsa = null;
- if (sa.hasAdditionalAbility("Execute")) {
- trigsa = sa.getAdditionalAbility("Execute");
- } else {
- trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
+ SpellAbility trigsa = sa.getAdditionalAbility("Execute");
+ if (trigsa == null) {
+ return false;
}
+
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai);
@@ -56,12 +53,11 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
return true;
}
- SpellAbility trigsa = null;
- if (sa.hasAdditionalAbility("Execute")) {
- trigsa = sa.getAdditionalAbility("Execute");
- } else {
- trigsa = AbilityFactory.getAbility(sa.getHostCard(), sa.getParam("Execute"));
+ SpellAbility trigsa = sa.getAdditionalAbility("Execute");
+ if (trigsa == null) {
+ return false;
}
+
trigsa.setActivatingPlayer(ai);
return AiPlayDecision.WillPlay == ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(trigsa);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java
index 98e351bfdf5..78044dedaec 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java
@@ -146,7 +146,7 @@ public class LifeGainAi extends SpellAbilityAi {
}
// don't play if the conditions aren't met, unless it would trigger a
// beneficial sub-condition
- if (!activateForCost && !sa.getConditions().areMet(sa)) {
+ if (!activateForCost && !sa.metConditions()) {
final AbilitySub abSub = sa.getSubAbility();
if (abSub != null && !sa.isWrapper() && "True".equals(source.getSVar("AIPlayForSub"))) {
if (!abSub.getConditions().areMet(abSub)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java
index bd4ea2d6091..f3ec36f837d 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java
@@ -279,7 +279,7 @@ public class PermanentAi extends SpellAbilityAi {
final Card source = sa.getHostCard();
final Cost cost = sa.getPayCosts();
- if (sa.getConditions() != null && !sa.getConditions().areMet(sa)) {
+ if (!sa.metConditions()) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
index 9c743f30653..8cf8c3d9d0d 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
@@ -6,7 +6,6 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.LobbyPlayer;
import forge.ai.LobbyPlayerAi;
-import forge.card.CardStateName;
import forge.game.*;
import forge.game.card.*;
import forge.game.card.token.TokenInfo;
@@ -289,16 +288,9 @@ public class GameCopier {
newCard.setTapped(true);
}
if (c.isFaceDown()) {
- boolean isCreature = newCard.isCreature();
- boolean hasManaCost = !newCard.getManaCost().isNoCost();
newCard.turnFaceDown(true);
if (c.isManifested()) {
newCard.setManifested(true);
- // TODO: Should be able to copy other abilities...
- if (isCreature && hasManaCost) {
- newCard.getState(CardStateName.Original).addSpellAbility(
- CardFactoryUtil.abilityManifestFaceUp(newCard, newCard.getManaCost()));
- }
}
}
if (c.isMonstrous()) {
diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java
index b57a51830e4..927b315bf00 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java
@@ -16,8 +16,6 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetChoices;
-import forge.game.spellability.TargetRestrictions;
-import forge.util.TextUtil;
public class GameSimulator {
public static boolean COPY_STACK = false;
@@ -125,11 +123,13 @@ public class GameSimulator {
}
private SpellAbility findSaInSimGame(SpellAbility sa) {
+ // is already an ability from sim game
+ if (sa.getHostCard().getGame().equals(this.simGame)) {
+ return sa;
+ }
Card origHostCard = sa.getHostCard();
Card hostCard = (Card) copier.find(origHostCard);
String desc = sa.getDescription();
- // FIXME: This is a hack that makes testManifest pass - figure out why it's needed.
- desc = TextUtil.fastReplace(desc, "Unmanifest {0}", "Unmanifest no cost");
for (SpellAbility cSa : hostCard.getSpellAbilities()) {
if (desc.startsWith(cSa.getDescription())) {
return cSa;
@@ -163,14 +163,12 @@ public class GameSimulator {
SpellAbility saOrSubSa = sa;
do {
if (origSaOrSubSa.usesTargeting()) {
- final boolean divided = origSaOrSubSa.hasParam("DividedAsYouChoose");
- final TargetRestrictions origTgtRes = origSaOrSubSa.getTargetRestrictions();
- final TargetRestrictions tgtRes = saOrSubSa.getTargetRestrictions();
+ final boolean divided = origSaOrSubSa.isDividedAsYouChoose();
for (final GameObject o : origSaOrSubSa.getTargets()) {
final GameObject target = copier.find(o);
saOrSubSa.getTargets().add(target);
if (divided) {
- tgtRes.addDividedAllocation(target, origTgtRes.getDividedValue(o));
+ saOrSubSa.addDividedAllocation(target, origSaOrSubSa.getDividedValue(o));
}
}
}
diff --git a/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java b/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java
index 164e564a15e..18e158e9a23 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java
@@ -173,16 +173,15 @@ public class PossibleTargetSelector {
// Divide up counters, since AI is expected to do this. For now,
// divided evenly with left-overs going to the first target.
- if (targetingSa.hasParam("DividedAsYouChoose")) {
+ if (targetingSa.isDividedAsYouChoose()) {
final int targetCount = targetingSa.getTargets().getTargetCards().size();
if (targetCount > 0) {
final String amountStr = targetingSa.getParam("CounterNum");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, targetingSa);
final int amountPerCard = amount / targetCount;
int amountLeftOver = amount - (amountPerCard * targetCount);
- final TargetRestrictions tgtRes = targetingSa.getTargetRestrictions();
for (GameObject target : targetingSa.getTargets()) {
- tgtRes.addDividedAllocation(target, amountPerCard + amountLeftOver);
+ targetingSa.addDividedAllocation(target, amountPerCard + amountLeftOver);
amountLeftOver = 0;
}
}
diff --git a/forge-core/src/main/java/forge/util/FileUtil.java b/forge-core/src/main/java/forge/util/FileUtil.java
index 212748744b1..00af3693cb9 100644
--- a/forge-core/src/main/java/forge/util/FileUtil.java
+++ b/forge-core/src/main/java/forge/util/FileUtil.java
@@ -286,4 +286,11 @@ public final class FileUtil {
}, 5000); //abort reading file if it takes longer than 5 seconds
return lines;
}
+
+ public static String getParent(final String resourcePath) {
+ File f = new File(resourcePath);
+ if (f.getParentFile().getName() != null)
+ return f.getParentFile().getName();
+ return "";
+ }
}
diff --git a/forge-game/src/main/java/forge/game/CardTraitPredicates.java b/forge-game/src/main/java/forge/game/CardTraitPredicates.java
index 3e9115aee38..c89e0556648 100644
--- a/forge-game/src/main/java/forge/game/CardTraitPredicates.java
+++ b/forge-game/src/main/java/forge/game/CardTraitPredicates.java
@@ -2,8 +2,19 @@ package forge.game;
import com.google.common.base.Predicate;
+import forge.game.card.Card;
+
public class CardTraitPredicates {
+ public static final Predicate isHostCard(final Card host) {
+ return new Predicate() {
+ @Override
+ public boolean apply(final CardTraitBase sa) {
+ return host.equals(sa.getHostCard());
+ }
+ };
+ }
+
public static final Predicate hasParam(final String name) {
return new Predicate() {
@Override
diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java
index 0565c8981e5..26ecb7d6980 100644
--- a/forge-game/src/main/java/forge/game/ForgeScript.java
+++ b/forge-game/src/main/java/forge/game/ForgeScript.java
@@ -5,6 +5,7 @@ import forge.card.MagicColor;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardState;
+import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
@@ -131,6 +132,9 @@ public class ForgeScript {
return !sa.isManaAbility();
} else if (property.equals("withoutXCost")) {
return !sa.costHasManaX();
+ } else if (property.equals("hasTapCost")) {
+ Cost cost = sa.getPayCosts();
+ return cost != null && cost.hasTapCost();
} else if (property.equals("Buyback")) {
return sa.isBuyBackAbility();
} else if (property.equals("Cycling")) {
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index 403817ebec0..f065694166c 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -207,7 +207,7 @@ public class GameAction {
if (!c.isRealToken()) {
copied = CardFactory.copyCard(c, false);
- if (fromBattlefield) {
+ if (!zoneTo.is(ZoneType.Stack)) {
// when a card leaves the battlefield, ensure it's in its original state
// (we need to do this on the object before copying it, or it won't work correctly e.g.
// on Transformed objects)
diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java
index e61cd797a85..6c50fa4e18c 100644
--- a/forge-game/src/main/java/forge/game/GameActionUtil.java
+++ b/forge-game/src/main/java/forge/game/GameActionUtil.java
@@ -17,7 +17,6 @@
*/
package forge.game;
-import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -252,6 +251,18 @@ public final class GameActionUtil {
}
}
+ if (sa.isManaAbility() && sa.isActivatedAbility() && activator.hasKeyword("Piracy") && source.isLand() && source.isInPlay() && !activator.equals(source.getController()) && sa.getPayCosts().hasTapCost()) {
+ SpellAbility newSA = sa.copy(activator);
+ // to bypass Activator restriction, set Activator to Player
+ sa.getRestrictions().setActivator("Player");
+
+ // extra Mana restriction to only Spells
+ for (AbilityManaPart mp : newSA.getAllManaParts()) {
+ mp.setExtraManaRestriction("Spell");
+ }
+ alternatives.add(newSA);
+ }
+
// below are for some special cases of activated abilities
if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) {
for (final KeywordInterface inst : source.getKeywords()) {
@@ -277,7 +288,6 @@ public final class GameActionUtil {
newSA.setDescription(sb.toString());
alternatives.add(newSA);
- break;
}
}
@@ -473,62 +483,12 @@ public final class GameActionUtil {
int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE);
if (v > 0) {
-
- final Card eff = new Card(game.nextCardId(), game);
- eff.setTimestamp(game.getNextTimestamp());
- eff.setName(c.getName() + "'s Effect");
- eff.addType("Effect");
- eff.setOwner(activator);
-
- eff.setImageKey(c.getImageKey());
- eff.setColor(MagicColor.COLORLESS);
- eff.setImmutable(true);
- // try to get the SpellAbility from the mana ability
- //eff.setEffectSource((SpellAbility)null);
-
- eff.addRemembered(host);
-
- String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | ETB$ True | CounterNum$ " + v;
-
- SpellAbility saAb = AbilityFactory.getAbility(abStr, c);
-
- CardFactoryUtil.setupETBReplacementAbility(saAb);
-
- String desc = "It enters the battlefield with ";
- desc += Lang.nounWithNumeral(v, CounterEnumType.P1P1.getName() + " counter");
- desc += " on it.";
-
- String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc;
-
- ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
- re.setLayer(ReplacementLayer.Other);
- re.setOverridingAbility(saAb);
-
- eff.addReplacementEffect(re);
-
- // Forgot Trigger
- String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True";
- String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard";
- String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"
- + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0";
-
- SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff);
- AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff);
- saForget.setSubAbility(saExile);
-
- final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true);
- parsedTrigger.setOverridingAbility(saForget);
- eff.addTrigger(parsedTrigger);
- eff.updateStateForView();
-
- // TODO: Add targeting to the effect so it knows who it's dealing with
- game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
- game.getAction().moveTo(ZoneType.Command, eff, null);
- game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
+ Card eff = createETBCountersEffect(c, host, activator, "P1P1", String.valueOf(v));
if (result == null) {
result = sa.copy();
}
+ result.addRollbackEffect(eff);
for (int i = 0; i < v; i++) {
result.getPayCosts().add(cost);
}
@@ -546,45 +506,85 @@ public final class GameActionUtil {
return result != null ? result : sa;
}
- private static boolean hasUrzaLands(final Player p) {
- final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield);
- return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine")))
- && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Power-Plant")))
- && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Tower")));
+ public static Card createETBCountersEffect(Card sourceCard, Card c, Player controller, String counter, String amount) {
+ final Game game = sourceCard.getGame();
+ final Card eff = new Card(game.nextCardId(), game);
+ eff.setTimestamp(game.getNextTimestamp());
+ eff.setName(sourceCard.getName() + "'s Effect");
+ eff.addType("Effect");
+ eff.setOwner(controller);
+
+ eff.setImageKey(sourceCard.getImageKey());
+ eff.setColor(MagicColor.COLORLESS);
+ eff.setImmutable(true);
+ // try to get the SpellAbility from the mana ability
+ //eff.setEffectSource((SpellAbility)null);
+
+ eff.addRemembered(c);
+
+ String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ " + counter
+ + " | ETB$ True | CounterNum$ " + amount;
+
+ SpellAbility sa = AbilityFactory.getAbility(abStr, c);
+ if (!StringUtils.isNumeric(amount)) {
+ sa.setSVar(amount, sourceCard.getSVar(amount));
+ }
+ CardFactoryUtil.setupETBReplacementAbility(sa);
+
+ String desc = "It enters the battlefield with ";
+ desc += Lang.nounWithNumeral(amount, CounterType.getType(counter).getName() + " counter");
+ desc += " on it.";
+
+ String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc;
+
+ ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
+ re.setLayer(ReplacementLayer.Other);
+ re.setOverridingAbility(sa);
+
+ eff.addReplacementEffect(re);
+
+ // Forgot Trigger
+ String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True";
+ String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard";
+ String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"
+ + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0";
+
+ SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff);
+ AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff);
+ saForget.setSubAbility(saExile);
+
+ final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true);
+ parsedTrigger.setOverridingAbility(saForget);
+ eff.addTrigger(parsedTrigger);
+ eff.updateStateForView();
+
+ // TODO: Add targeting to the effect so it knows who it's dealing with
+ game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
+ game.getAction().moveTo(ZoneType.Command, eff, null);
+ game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
+
+ return eff;
}
- public static int amountOfManaGenerated(final SpellAbility sa, boolean multiply) {
- // Calculate generated mana here for stack description and resolving
-
- int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa) : 1;
- AbilityManaPart abMana = sa.getManaPartRecursive();
-
- if (sa.hasParam("Bonus")) {
- // For mana abilities that get a bonus
- // Bonus currently MULTIPLIES the base amount. Base Amounts should
- // ALWAYS be Base
- int bonus = 0;
- if (sa.getParam("Bonus").equals("UrzaLands")) {
- if (hasUrzaLands(sa.getActivatingPlayer())) {
- bonus = Integer.parseInt(sa.getParam("BonusProduced"));
- }
+ public static String generatedTotalMana(final SpellAbility sa) {
+ StringBuilder sb = new StringBuilder();
+ SpellAbility tail = sa;
+ while (tail != null) {
+ String value = generatedMana(tail);
+ if (!value.isEmpty() && !"0".equals(value)) {
+ sb.append(value).append(" ");
}
-
- amount += bonus;
- }
-
- if (!multiply || abMana.isAnyMana() || abMana.isComboMana() || abMana.isSpecialMana()) {
- return amount;
- } else {
- // For cards that produce like {C}{R} vs cards that produce {R}{R}.
- return abMana.mana().split(" ").length * amount;
+ tail = tail.getSubAbility();
}
+ return sb.toString().trim();
}
-
public static String generatedMana(final SpellAbility sa) {
- int amount = amountOfManaGenerated(sa, false);
+ int amount = sa.amountOfManaGenerated(false);
AbilityManaPart abMana = sa.getManaPart();
+ if (abMana == null) {
+ return "";
+ }
String baseMana;
if (abMana.isComboMana()) {
diff --git a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java b/forge-game/src/main/java/forge/game/TriggerReplacementBase.java
index 1ff5b94e3ae..0764bd7b2fc 100644
--- a/forge-game/src/main/java/forge/game/TriggerReplacementBase.java
+++ b/forge-game/src/main/java/forge/game/TriggerReplacementBase.java
@@ -77,4 +77,5 @@ public abstract class TriggerReplacementBase extends CardTraitBase implements II
this.overridingAbility = overridingAbility0;
}
+ abstract public SpellAbility ensureAbility();
}
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java b/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java
index a471e78703a..4603cee8d98 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityApiBased.java
@@ -1,9 +1,5 @@
package forge.game.ability;
-import forge.game.ability.effects.ChangeZoneAllEffect;
-import forge.game.ability.effects.ChangeZoneEffect;
-import forge.game.ability.effects.ManaEffect;
-import forge.game.ability.effects.ManaReflectedEffect;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.spellability.AbilityActivated;
@@ -15,8 +11,6 @@ import java.util.Map;
public class AbilityApiBased extends AbilityActivated {
private final SpellAbilityEffect effect;
- private static final long serialVersionUID = -4183793555528531978L;
-
public AbilityApiBased(ApiType api0, Card sourceCard, Cost abCost, TargetRestrictions tgt, Map params0) {
super(sourceCard, abCost, tgt);
originalMapParams.putAll(params0);
@@ -24,13 +18,12 @@ public class AbilityApiBased extends AbilityActivated {
api = api0;
effect = api.getSpellEffect();
- if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
-
+ if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) {
this.setManaPart(new AbilityManaPart(sourceCard, mapParams));
this.setUndoable(true); // will try at least
}
- if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) {
+ if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) {
AbilityFactory.adjustChangeZoneTarget(mapParams, this);
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
index b64e55edf59..9ccdb5315a2 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
@@ -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")));
}
@@ -364,10 +364,6 @@ public final class AbilityFactory {
if (mapParams.containsKey("TargetsWithDifferentCMC")) {
abTgt.setDifferentCMC(true);
}
- if (mapParams.containsKey("DividedAsYouChoose")) {
- abTgt.calculateStillToDivide(mapParams.get("DividedAsYouChoose"), null, null);
- abTgt.setDividedAsYouChoose(true);
- }
if (mapParams.containsKey("TargetsAtRandom")) {
abTgt.setRandomTarget(true);
}
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
index a4179f8ba29..407fa8dc409 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
@@ -1400,7 +1400,7 @@ public class AbilityUtils {
);
// check conditions
- if (sa.getConditions().areMet(sa)) {
+ if (sa.metConditions()) {
if (sa.isWrapper() || StringUtils.isBlank(sa.getParam("UnlessCost"))) {
sa.resolve();
}
@@ -1668,10 +1668,15 @@ public class AbilityUtils {
// Count$Kicked..
if (sq[0].startsWith("Kicked")) {
- boolean kicked = ((SpellAbility)ctb).isKicked() || c.getKickerMagnitude() > 0;
+ boolean kicked = sa.isKicked() || c.getKickerMagnitude() > 0;
return CardFactoryUtil.doXMath(Integer.parseInt(kicked ? sq[1] : sq[2]), expr, c);
}
+ // Count$UrzaLands..
+ if (sq[0].startsWith("UrzaLands")) {
+ return CardFactoryUtil.doXMath(Integer.parseInt(sa.getActivatingPlayer().hasUrzaLands() ? sq[1] : sq[2]), expr, c);
+ }
+
//Count$SearchedLibrary.
if (sq[0].contains("SearchedLibrary")) {
int sum = 0;
diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java
index 8df8f0f2a0c..74a3a612e63 100644
--- a/forge-game/src/main/java/forge/game/ability/ApiType.java
+++ b/forge-game/src/main/java/forge/game/ability/ApiType.java
@@ -136,6 +136,7 @@ public enum ApiType {
Repeat (RepeatEffect.class),
RepeatEach (RepeatEachEffect.class),
ReplaceEffect (ReplaceEffect.class),
+ ReplaceMana (ReplaceManaEffect.class),
ReplaceDamage (ReplaceDamageEffect.class),
ReplaceSplitDamage (ReplaceSplitDamageEffect.class),
RestartGame (RestartGameEffect.class),
diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
index 381ef080e5a..4f492f439ff 100644
--- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
@@ -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));
diff --git a/forge-game/src/main/java/forge/game/ability/SpellApiBased.java b/forge-game/src/main/java/forge/game/ability/SpellApiBased.java
index d96401336a4..d7903a2c502 100644
--- a/forge-game/src/main/java/forge/game/ability/SpellApiBased.java
+++ b/forge-game/src/main/java/forge/game/ability/SpellApiBased.java
@@ -2,10 +2,6 @@ package forge.game.ability;
import java.util.Map;
-import forge.game.ability.effects.ChangeZoneAllEffect;
-import forge.game.ability.effects.ChangeZoneEffect;
-import forge.game.ability.effects.ManaEffect;
-import forge.game.ability.effects.ManaReflectedEffect;
import forge.game.card.Card;
import forge.game.cost.Cost;
import forge.game.spellability.AbilityManaPart;
@@ -28,11 +24,11 @@ public class SpellApiBased extends Spell {
// A spell is always intrinsic
this.setIntrinsic(true);
- if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
+ if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) {
this.setManaPart(new AbilityManaPart(sourceCard, mapParams));
}
- if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) {
+ if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) {
AbilityFactory.adjustChangeZoneTarget(mapParams, this);
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
index 68b50dfae39..86ad24f6249 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
@@ -159,8 +159,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
// Grant triggers
final List 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 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
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java
index 56477884311..c52942c35d5 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeTargetsEffect.java
@@ -1,9 +1,11 @@
package forge.game.ability.effects;
+import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.game.GameEntity;
import forge.game.GameObject;
+import forge.game.GameObjectPredicates;
import forge.game.ability.SpellAbilityEffect;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -19,7 +21,7 @@ import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
import java.util.List;
-/**
+/**
* TODO: Write javadoc for this type.
*
*/
@@ -42,9 +44,6 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
continue;
}
- boolean changesOneTarget = sa.hasParam("ChangeSingleTarget"); // The only card known to replace targets with self is Spellskite
- // There is also Muck Drubb but it replaces ALL occurences of a single target with itself (unlike Spellskite that has to be activated for each).
-
SpellAbilityStackInstance changingTgtSI = si;
Player chooser = sa.getActivatingPlayer();
@@ -54,11 +53,11 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
if (isOptional && !chooser.getController().confirmAction(sa, null, Localizer.getInstance().getMessage("lblDoYouWantChangeAbilityTargets", tgtSA.getHostCard().toString()))) {
continue;
}
- if (changesOneTarget) {
+ if (sa.hasParam("ChangeSingleTarget")) {
// 1. choose a target of target spell
List> allTargets = new ArrayList<>();
while(changingTgtSI != null) {
- SpellAbility changedSa = changingTgtSI.getSpellAbility(true);
+ SpellAbility changedSa = changingTgtSI.getSpellAbility(true);
if (changedSa.usesTargeting()) {
for(GameObject it : changedSa.getTargets())
allTargets.add(ImmutablePair.of(changingTgtSI, it));
@@ -77,6 +76,8 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
GameObject oldTarget = chosenTarget.getValue();
TargetChoices oldTargetBlock = replaceIn.getTargetChoices();
TargetChoices newTargetBlock = oldTargetBlock.clone();
+ // gets the divied value from old target
+ Integer div = oldTargetBlock.getDividedValue(oldTarget);
newTargetBlock.remove(oldTarget);
replaceIn.updateTarget(newTargetBlock);
// 3. test if updated choices would be correct.
@@ -84,7 +85,10 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
if (replaceIn.getSpellAbility(true).canTarget(newTarget)) {
newTargetBlock.add(newTarget);
- replaceIn.updateTarget(newTargetBlock, oldTarget, newTarget);
+ if (div != null) {
+ newTargetBlock.addDividedAllocation(newTarget, div);
+ }
+ replaceIn.updateTarget(newTargetBlock);
}
else {
replaceIn.updateTarget(oldTargetBlock);
@@ -93,36 +97,38 @@ public class ChangeTargetsEffect extends SpellAbilityEffect {
else {
while(changingTgtSI != null) {
SpellAbility changingTgtSA = changingTgtSI.getSpellAbility(true);
- if (sa.hasParam("RandomTarget")){
- if (changingTgtSA.usesTargeting()) {
+ if (changingTgtSA.usesTargeting()) {
+ // random target and DefinedMagnet works on single targets
+ if (sa.hasParam("RandomTarget")){
+ int div = changingTgtSA.getTotalDividedValue();
changingTgtSA.resetTargets();
List candidates = changingTgtSA.getTargetRestrictions().getAllCandidates(changingTgtSA, true);
GameEntity choice = Aggregates.random(candidates);
changingTgtSA.getTargets().add(choice);
- changingTgtSI.updateTarget(changingTgtSA.getTargets(), null, choice);
+ if (changingTgtSA.isDividedAsYouChoose()) {
+ changingTgtSA.addDividedAllocation(choice, div);
+ }
+
+ changingTgtSI.updateTarget(changingTgtSA.getTargets());
}
- }
- else if (sa.hasParam("DefinedMagnet")){
- GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
- if (newTarget != null && changingTgtSA.canTarget(newTarget)) {
- changingTgtSA.resetTargets();
- changingTgtSA.getTargets().add(newTarget);
- changingTgtSI.updateTarget(changingTgtSA.getTargets(), null, newTarget);
- }
- }
- else {
- // Update targets, with a potential new target
- TargetChoices newTarget = sa.getActivatingPlayer().getController().chooseNewTargetsFor(changingTgtSA);
- if (null != newTarget) {
- if (sa.hasParam("TargetRestriction")) {
- if (newTarget.getFirstTargetedCard() != null && newTarget.getFirstTargetedCard().
- isValid(sa.getParam("TargetRestriction").split(","), activator, sa.getHostCard(), sa)) {
- changingTgtSI.updateTarget(newTarget);
- } else if (newTarget.getFirstTargetedPlayer() != null && newTarget.getFirstTargetedPlayer().
- isValid(sa.getParam("TargetRestriction").split(","), activator, sa.getHostCard(), sa)) {
- changingTgtSI.updateTarget(newTarget);
+ else if (sa.hasParam("DefinedMagnet")){
+ GameObject newTarget = Iterables.getFirst(getDefinedCardsOrTargeted(sa, "DefinedMagnet"), null);
+ if (newTarget != null && changingTgtSA.canTarget(newTarget)) {
+ int div = changingTgtSA.getTotalDividedValue();
+ changingTgtSA.resetTargets();
+ changingTgtSA.getTargets().add(newTarget);
+ changingTgtSI.updateTarget(changingTgtSA.getTargets());
+ if (changingTgtSA.isDividedAsYouChoose()) {
+ changingTgtSA.addDividedAllocation(newTarget, div);
}
- } else {
+ }
+ }
+ else {
+ // Update targets, with a potential new target
+ Predicate filter = sa.hasParam("TargetRestriction") ? GameObjectPredicates.restriction(sa.getParam("TargetRestriction").split(","), activator, sa.getHostCard(), sa) : null;
+ // TODO Creature.Other might not work yet as it should
+ TargetChoices newTarget = sa.getActivatingPlayer().getController().chooseNewTargetsFor(changingTgtSA, filter, false);
+ if (null != newTarget) {
changingTgtSI.updateTarget(newTarget);
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java
index fd33dc41397..719c4f63ad4 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java
@@ -108,6 +108,8 @@ public class CloneEffect extends SpellAbilityEffect {
if (!cloneTargets.isEmpty()) {
tgtCard = cloneTargets.get(0);
game.getTriggerHandler().clearActiveTriggers(tgtCard, null);
+ } else {
+ return;
}
} else if (sa.hasParam("Choices") && sa.usesTargeting()) {
tgtCard = sa.getTargets().getFirstTargetedCard();
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java
index 3c1ea43e9f3..50a18e79e60 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CopySpellAbilityEffect.java
@@ -111,7 +111,7 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
candidates.remove(p);
for (GameEntity o : candidates) {
- SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
+ SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
resetFirstTargetOnCopy(copy, o, targetedSA);
copies.add(copy);
}
@@ -144,12 +144,12 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
}
for (final Card c : valid) {
- SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
+ SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
resetFirstTargetOnCopy(copy, c, targetedSA);
copies.add(copy);
}
for (final Player p : players) {
- SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
+ SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
resetFirstTargetOnCopy(copy, p, targetedSA);
copies.add(copy);
}
@@ -157,12 +157,9 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
}
else {
for (int i = 0; i < amount; i++) {
- SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
+ SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
if (sa.hasParam("MayChooseTarget")) {
copy.setMayChooseNewTargets(true);
- if (copy.usesTargeting()) {
- copy.getTargetRestrictions().setMandatory(true);
- }
}
// extra case for Epic to remove the keyword and the last part of the SpellAbility
@@ -206,12 +203,9 @@ public class CopySpellAbilityEffect extends SpellAbilityEffect {
}
int extraAmount = addAmount - copies.size();
for (int i = 0; i < extraAmount; i++) {
- SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA);
+ SpellAbility copy = CardFactory.copySpellAbilityAndPossiblyHost(sa, chosenSA, controller);
// extra copies added with CopySpellReplacenment currently always has new choose targets
copy.setMayChooseNewTargets(true);
- if (copy.usesTargeting()) {
- copy.getTargetRestrictions().setMandatory(true);
- }
copies.add(copy);
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java
index 9bef78eab96..598fc58446e 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersMoveEffect.java
@@ -27,13 +27,12 @@ public class CountersMoveEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
- final Card host = sa.getHostCard();
final StringBuilder sb = new StringBuilder();
final List tgtCards = getDefinedCardsOrTargeted(sa);
Card source = null;
- if (sa.usesTargeting() && sa.getTargetRestrictions().getMinTargets(host, sa) == 2) {
+ if (sa.usesTargeting() && sa.getMinTargets() == 2) {
if (tgtCards.size() < 2) {
return "";
}
@@ -241,7 +240,7 @@ public class CountersMoveEffect extends SpellAbilityEffect {
Card source = null;
List tgtCards = getDefinedCardsOrTargeted(sa);
// special logic for moving from Target to Target
- if (sa.usesTargeting() && sa.getTargetRestrictions().getMinTargets(host, sa) == 2) {
+ if (sa.usesTargeting() && sa.getMinTargets() == 2) {
if (tgtCards.size() < 2) {
return;
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java
index d3ad905eaf2..1dad1191eef 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java
@@ -46,7 +46,6 @@ public class CountersPutEffect extends SpellAbilityEffect {
protected String getStackDescription(SpellAbility spellAbility) {
final StringBuilder stringBuilder = new StringBuilder();
final Card card = spellAbility.getHostCard();
- final boolean dividedAsYouChoose = spellAbility.hasParam("DividedAsYouChoose");
final int amount = AbilityUtils.calculateAmount(card, spellAbility.getParamOrDefault("CounterNum", "1"), spellAbility);
//skip the StringBuilder if no targets are chosen ("up to" scenario)
@@ -60,7 +59,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
stringBuilder.append("Bolster ").append(amount);
return stringBuilder.toString();
}
- if (dividedAsYouChoose) {
+ if (spellAbility.isDividedAsYouChoose()) {
stringBuilder.append("Distribute ");
} else {
stringBuilder.append("Put ");
@@ -84,7 +83,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
stringBuilder.append("s");
}
- if (dividedAsYouChoose) {
+ if (spellAbility.isDividedAsYouChoose()) {
stringBuilder.append(" among ");
} else {
stringBuilder.append(" on ");
@@ -96,8 +95,9 @@ public class CountersPutEffect extends SpellAbilityEffect {
for(int i = 0; i < targetCards.size(); i++) {
Card targetCard = targetCards.get(i);
stringBuilder.append(targetCard);
- if (spellAbility.getTargetRestrictions().getDividedMap().get(targetCard) != null) // fix null counter stack description
- stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" counter)");
+ Integer v = spellAbility.getDividedValue(targetCard);
+ if (v != null) // fix null counter stack description
+ stringBuilder.append(" (").append(v).append(" counter)");
if(i == targetCards.size() - 2) {
stringBuilder.append(" and ");
@@ -259,7 +259,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
if (obj instanceof Card) {
boolean counterAdded = false;
- counterAmount = sa.usesTargeting() && sa.hasParam("DividedAsYouChoose") ? sa.getTargetRestrictions().getDividedValue(gameCard) : counterAmount;
+ counterAmount = sa.usesTargeting() && sa.isDividedAsYouChoose() ? sa.getDividedValue(gameCard) : counterAmount;
if (!sa.usesTargeting() || gameCard.canBeTargetedBy(sa)) {
if (max != -1) {
counterAmount = Math.max(Math.min(max - gameCard.getCounters(counterType), counterAmount), 0);
@@ -270,7 +270,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
params.put("CounterType", counterType);
counterAmount = pc.chooseNumber(sa, Localizer.getInstance().getMessage("lblHowManyCounters"), 0, counterAmount, params);
}
- if (sa.hasParam("DividedAsYouChoose") && !sa.usesTargeting()) {
+ if (sa.isDividedAsYouChoose() && !sa.usesTargeting()) {
Map params = Maps.newHashMap();
params.put("Target", obj);
params.put("CounterType", counterType);
@@ -378,7 +378,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
card.addRemembered(gameCard);
}
game.updateLastStateForCard(gameCard);
- if (sa.hasParam("DividedAsYouChoose") && !sa.usesTargeting()) {
+ if (sa.isDividedAsYouChoose() && !sa.usesTargeting()) {
counterRemain = counterRemain - counterAmount;
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java
index 5f0b4608c55..5b9e0542f5e 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java
@@ -61,7 +61,7 @@ public class DamageDealEffect extends DamageBaseEffect {
if (spellAbility.usesTargeting()) {
if (spellAbility.hasParam("DivideEvenly")) {
stringBuilder.append("divided evenly (rounded down) to\n");
- } else if (spellAbility.hasParam("DividedAsYouChoose")) {
+ } else if (spellAbility.isDividedAsYouChoose()) {
stringBuilder.append("divided to\n");
} else
stringBuilder.append("to ");
@@ -75,8 +75,9 @@ public class DamageDealEffect extends DamageBaseEffect {
for (int i = 0; i < targetCards.size(); i++) {
Card targetCard = targetCards.get(i);
stringBuilder.append(targetCard);
- if (spellAbility.getTargetRestrictions().getDividedMap().get(targetCard) != null) //fix null damage stack description
- stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetCard)).append(" damage)");
+ Integer v = spellAbility.getDividedValue(targetCard);
+ if (v != null) //fix null damage stack description
+ stringBuilder.append(" (").append(v).append(" damage)");
if (i == targetCount - 2) {
stringBuilder.append(" and ");
@@ -89,8 +90,9 @@ public class DamageDealEffect extends DamageBaseEffect {
for (int i = 0; i < players.size(); i++) {
Player targetPlayer = players.get(i);
stringBuilder.append(targetPlayer);
- if (spellAbility.getTargetRestrictions().getDividedMap().get(targetPlayer) != null) //fix null damage stack description
- stringBuilder.append(" (").append(spellAbility.getTargetRestrictions().getDividedMap().get(targetPlayer)).append(" damage)");
+ Integer v = spellAbility.getDividedValue(targetPlayer);
+ if (v != null) //fix null damage stack description
+ stringBuilder.append(" (").append(v).append(" damage)");
if (i == players.size() - 2) {
stringBuilder.append(" and ");
@@ -102,7 +104,7 @@ public class DamageDealEffect extends DamageBaseEffect {
} else {
if (spellAbility.hasParam("DivideEvenly")) {
stringBuilder.append("divided evenly (rounded down) ");
- } else if (spellAbility.hasParam("DividedAsYouChoose")) {
+ } else if (spellAbility.isDividedAsYouChoose()) {
stringBuilder.append("divided as you choose ");
}
stringBuilder.append("to ").append(Lang.joinHomogenous(targets));
@@ -229,7 +231,7 @@ public class DamageDealEffect extends DamageBaseEffect {
for (final GameObject o : tgts) {
if (!removeDamage) {
- dmg = (sa.usesTargeting() && sa.hasParam("DividedAsYouChoose")) ? sa.getTargetRestrictions().getDividedValue(o) : dmg;
+ dmg = (sa.usesTargeting() && sa.isDividedAsYouChoose()) ? sa.getDividedValue(o) : dmg;
if (dmg <= 0) {
continue;
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java
index 710f2f747b6..54e0395a34e 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java
@@ -25,7 +25,7 @@ public class DamagePreventEffect extends SpellAbilityEffect {
sb.append("Prevent the next ");
sb.append(sa.getParam("Amount"));
sb.append(" damage that would be dealt ");
- if (sa.hasParam("DividedAsYouChoose")) {
+ if (sa.isDividedAsYouChoose()) {
sb.append("between ");
} else {
sb.append("to ");
@@ -75,8 +75,8 @@ public class DamagePreventEffect extends SpellAbilityEffect {
final boolean targeted = (sa.usesTargeting());
final boolean preventionWithEffect = sa.hasParam("PreventionSubAbility");
- for (final Object o : tgts) {
- numDam = (sa.usesTargeting() && sa.hasParam("DividedAsYouChoose")) ? sa.getTargetRestrictions().getDividedValue(o) : numDam;
+ for (final GameObject o : tgts) {
+ numDam = (sa.usesTargeting() && sa.isDividedAsYouChoose()) ? sa.getDividedValue(o) : numDam;
if (o instanceof Card) {
final Card c = (Card) o;
if (c.isInPlay() && (!targeted || c.canBeTargetedBy(sa))) {
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 b8172eaad7f..bb2e7afc4d4 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
@@ -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);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java
index d7e1e7fec74..20723feba63 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java
@@ -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);
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 959190819d2..b44158828dd 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
@@ -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);
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java
index 0a7ab66ac8d..74f6a3bdc3a 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java
@@ -15,7 +15,6 @@ import forge.game.mana.Mana;
import forge.game.player.Player;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility;
-import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Localizer;
@@ -37,7 +36,6 @@ public class ManaEffect extends SpellAbilityEffect {
sa.setUndoable(sa.isAbility() && sa.isUndoable());
final List tgtPlayers = getTargetPlayers(sa);
- final TargetRestrictions tgt = sa.getTargetRestrictions();
final boolean optional = sa.hasParam("Optional");
final Game game = sa.getActivatingPlayer().getGame();
@@ -45,46 +43,26 @@ public class ManaEffect extends SpellAbilityEffect {
return;
}
- if (sa.hasParam("DoubleManaInPool")) {
- for (final Player player : tgtPlayers) {
- for (byte color : ManaAtom.MANATYPES) {
- int amountColor = player.getManaPool().getAmountOfColor(color);
- for (int i = 0; i < amountColor; i++) {
- abMana.produceMana(MagicColor.toShortString(color), player, sa);
- }
- }
+ for (Player p : tgtPlayers) {
+ if (sa.usesTargeting() && !p.canBeTargetedBy(sa)) {
+ // Illegal target. Skip.
+ continue;
}
- }
- if (sa.hasParam("ProduceNoOtherMana")) {
- return;
- }
-
- if (abMana.isComboMana()) {
- for (Player p : tgtPlayers) {
+ if (abMana.isComboMana()) {
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1;
- if (tgt != null && !p.canBeTargetedBy(sa)) {
- // Illegal target. Skip.
- continue;
- }
- Player activator = sa.getActivatingPlayer();
String express = abMana.getExpressChoice();
String[] colorsProduced = abMana.getComboColors().split(" ");
final StringBuilder choiceString = new StringBuilder();
- ColorSet colorOptions = null;
+ ColorSet colorOptions = ColorSet.fromNames(colorsProduced);
String[] colorsNeeded = express.isEmpty() ? null : express.split(" ");
- if (!abMana.isAnyMana()) {
- colorOptions = ColorSet.fromNames(colorsProduced);
- } else {
- colorOptions = ColorSet.fromNames(MagicColor.Constant.ONLY_COLORS);
- }
boolean differentChoice = abMana.getOrigProduced().contains("Different");
ColorSet fullOptions = colorOptions;
for (int nMana = 0; nMana < amount; nMana++) {
String choice = "";
- if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible
+ if (colorsNeeded != null && colorsNeeded.length > nMana) { // select from express choices if possible
colorOptions = ColorSet
.fromMask(fullOptions.getColor() & ManaAtom.fromName(colorsNeeded[nMana]));
}
@@ -93,10 +71,10 @@ public class ManaEffect extends SpellAbilityEffect {
// just use the first possible color.
choice = colorsProduced[differentChoice ? nMana : 0];
} else {
- byte chosenColor = activator.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
+ byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa,
differentChoice ? fullOptions : colorOptions);
if (chosenColor == 0)
- throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + activator + " color mana choice is empty for " + card.getName());
+ throw new RuntimeException("ManaEffect::resolve() /*combo mana*/ - " + p + " color mana choice is empty for " + card.getName());
fullOptions = ColorSet.fromMask(fullOptions.getMyColor() - chosenColor);
choice = MagicColor.toShortString(chosenColor);
@@ -116,18 +94,10 @@ public class ManaEffect extends SpellAbilityEffect {
return;
}
- game.action.nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", activator.getName(), choiceString), activator);
+ game.getAction().nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), choiceString), p);
abMana.setExpressChoice(choiceString.toString());
}
- }
- else if (abMana.isAnyMana()) {
- for (Player p : tgtPlayers) {
- if (tgt != null && !p.canBeTargetedBy(sa)) {
- // Illegal target. Skip.
- continue;
- }
-
- Player act = sa.getActivatingPlayer();
+ else if (abMana.isAnyMana()) {
// AI color choice is set in ComputerUtils so only human players need to make a choice
String colorsNeeded = abMana.getExpressChoice();
@@ -142,20 +112,14 @@ public class ManaEffect extends SpellAbilityEffect {
colorMenu = mask == 0 ? ColorSet.ALL_COLORS : ColorSet.fromMask(mask);
byte val = p.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu);
if (0 == val) {
- throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + act + " color mana choice is empty for " + card.getName());
+ throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName());
}
choice = MagicColor.toShortString(val);
- game.action.nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", act.getName(), choice), act);
+ game.getAction().nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), choice), p);
abMana.setExpressChoice(choice);
}
- }
- else if (abMana.isSpecialMana()) {
- for (Player p : tgtPlayers) {
- if (tgt != null && !p.canBeTargetedBy(sa)) {
- // Illegal target. Skip.
- continue;
- }
+ else if (abMana.isSpecialMana()) {
String type = abMana.getOrigProduced().split("Special ")[1];
@@ -177,7 +141,7 @@ public class ManaEffect extends SpellAbilityEffect {
if (cs.isMonoColor())
sb.append(MagicColor.toShortString(s.getColorMask()));
else /* (cs.isMulticolor()) */ {
- byte chosenColor = sa.getActivatingPlayer().getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
+ byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
sb.append(MagicColor.toShortString(chosenColor));
}
}
@@ -210,34 +174,36 @@ public class ManaEffect extends SpellAbilityEffect {
abMana.setExpressChoice(ColorSet.fromMask(colors));
} else if (type.startsWith("EachColoredManaSymbol")) {
final String res = type.split("_")[1];
- final CardCollection list = AbilityUtils.getDefinedCards(card, res, sa);
StringBuilder sb = new StringBuilder();
- for (Card c : list) {
- String mana = c.getManaCost().toString();
- for (int i = 0; i < mana.length(); i++) {
- char symbol = mana.charAt(i);
- switch (symbol) {
- case 'W':
- case 'U':
- case 'B':
- case 'R':
- case 'G':
- sb.append(symbol).append(' ');
- break;
+ for (Card c : AbilityUtils.getDefinedCards(card, res, sa)) {
+ for (ManaCostShard s : c.getManaCost()) {
+ ColorSet cs = ColorSet.fromMask(s.getColorMask());
+ if(cs.isColorless())
+ continue;
+ sb.append(' ');
+ if (cs.isMonoColor())
+ sb.append(MagicColor.toShortString(s.getColorMask()));
+ else /* (cs.isMulticolor()) */ {
+ byte chosenColor = p.getController().chooseColor(Localizer.getInstance().getMessage("lblChooseSingleColorFromTarget", s.toString()), sa, cs);
+ sb.append(MagicColor.toShortString(chosenColor));
}
}
}
abMana.setExpressChoice(sb.toString().trim());
+ } else if (type.startsWith("DoubleManaInPool")) {
+ StringBuilder sb = new StringBuilder();
+ for (byte color : ManaAtom.MANATYPES) {
+ sb.append(StringUtils.repeat(MagicColor.toShortString(color), " ", p.getManaPool().getAmountOfColor(color))).append(" ");
+ }
+ abMana.setExpressChoice(sb.toString().trim());
}
if (abMana.getExpressChoice().isEmpty()) {
System.out.println("AbilityFactoryMana::manaResolve() - special mana effect is empty for " + sa.getHostCard().getName());
}
}
- }
- for (final Player player : tgtPlayers) {
- abMana.produceMana(GameActionUtil.generatedMana(sa), player, sa);
+ abMana.produceMana(GameActionUtil.generatedMana(sa), p, sa);
}
// Only clear express choice after mana has been produced
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java
index c5867722a2c..bdf69fbd81f 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java
@@ -11,7 +11,6 @@ import forge.game.spellability.SpellAbility;
import forge.util.Localizer;
import java.util.Collection;
-import java.util.List;
import org.apache.commons.lang3.StringUtils;
@@ -29,8 +28,7 @@ public class ManaReflectedEffect extends SpellAbilityEffect {
final Collection colors = CardUtil.getReflectableManaColors(sa);
- final List tgtPlayers = getTargetPlayers(sa);
- for (final Player player : tgtPlayers) {
+ for (final Player player : getTargetPlayers(sa)) {
final String generated = generatedReflectedMana(sa, colors, player);
ma.produceMana(generated, player, sa);
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java
index 17490640e78..09e91dd67e5 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java
@@ -1,7 +1,6 @@
package forge.game.ability.effects;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
@@ -149,14 +148,13 @@ public class PlayEffect extends SpellAbilityEffect {
if (sa.hasParam("ValidSA")) {
final String valid[] = {sa.getParam("ValidSA")};
- final Iterator itr = tgtCards.iterator();
- while (itr.hasNext()) {
- final Card c = itr.next();
- final List validSA = Lists.newArrayList(Iterables.filter(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa)));
- if (validSA.size() == 0) {
- itr.remove();
+ List toRemove = Lists.newArrayList();
+ for (Card c : tgtCards) {
+ if (!Iterables.any(AbilityUtils.getBasicSpellsFromPlayEffect(c, controller), SpellAbilityPredicates.isValid(valid, controller , c, sa))) {
+ toRemove.add(c);
}
}
+ tgtCards.removeAll(toRemove);
if (tgtCards.isEmpty()) {
return;
}
@@ -260,13 +258,8 @@ public class PlayEffect extends SpellAbilityEffect {
continue;
}
- final boolean noManaCost = sa.hasParam("WithoutManaCost");
- if (noManaCost) {
+ if (sa.hasParam("WithoutManaCost")) {
tgtSA = tgtSA.copyWithNoManaCost();
- // FIXME: a hack to get cards like Detonate only allow legal targets when cast without paying mana cost (with X=0).
- if (tgtSA.hasParam("ValidTgtsWithoutManaCost")) {
- tgtSA.getTargetRestrictions().changeValidTargets(tgtSA.getParam("ValidTgtsWithoutManaCost").split(","));
- }
} else if (sa.hasParam("PlayCost")) {
Cost abCost;
if ("ManaCost".equals(sa.getParam("PlayCost"))) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java
index 028980c02bf..eee8cdfbd0a 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceDamageEffect.java
@@ -6,12 +6,14 @@ import forge.game.ability.AbilityKey;
import org.apache.commons.lang3.StringUtils;
import forge.game.Game;
+import forge.game.GameLogEntryType;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
+import forge.util.TextUtil;
public class ReplaceDamageEffect extends SpellAbilityEffect {
@@ -58,6 +60,12 @@ public class ReplaceDamageEffect extends SpellAbilityEffect {
}
params.put(AbilityKey.DamageAmount, dmg);
+ // need to log Updated events there, or the log is wrong order
+ String message = sa.getReplacementEffect().toString();
+ if ( !StringUtils.isEmpty(message)) {
+ message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
+ game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
+ }
//try to call replacementHandler with new Params
ReplacementResult result = game.getReplacementHandler().run(event, params);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java
index af761de13fb..e15dd8731f1 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceEffect.java
@@ -3,9 +3,12 @@ package forge.game.ability.effects;
import java.util.List;
import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+
import com.google.common.collect.Maps;
import forge.game.Game;
+import forge.game.GameLogEntryType;
import forge.game.GameObject;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
@@ -16,6 +19,7 @@ import forge.game.player.Player;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
+import forge.util.TextUtil;
public class ReplaceEffect extends SpellAbilityEffect {
@@ -61,6 +65,13 @@ public class ReplaceEffect extends SpellAbilityEffect {
params.put(AbilityKey.EffectOnly, true);
}
+ // need to log Updated events there, or the log is wrong order
+ String message = sa.getReplacementEffect().toString();
+ if ( !StringUtils.isEmpty(message)) {
+ message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
+ game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
+ }
+
//try to call replacementHandler with new Params
ReplacementResult result = game.getReplacementHandler().run(retype, params);
switch (result) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java
new file mode 100644
index 00000000000..246a1857255
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java
@@ -0,0 +1,109 @@
+package forge.game.ability.effects;
+
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.google.common.collect.Maps;
+
+import forge.card.ColorSet;
+import forge.card.MagicColor;
+import forge.game.Game;
+import forge.game.GameLogEntryType;
+import forge.game.ability.AbilityKey;
+import forge.game.ability.SpellAbilityEffect;
+import forge.game.card.Card;
+import forge.game.player.Player;
+import forge.game.replacement.ReplacementResult;
+import forge.game.replacement.ReplacementType;
+import forge.game.spellability.SpellAbility;
+import forge.util.TextUtil;
+
+public class ReplaceManaEffect extends SpellAbilityEffect {
+
+ @Override
+ public void resolve(SpellAbility sa) {
+ final Card card = sa.getHostCard();
+ final Player player = sa.getActivatingPlayer();
+ final Game game = card.getGame();
+
+ // outside of Replacement Effect, unwanted result
+ if (!sa.isReplacementAbility()) {
+ return;
+ }
+
+ final ReplacementType event = sa.getReplacementEffect().getMode();
+ @SuppressWarnings("unchecked")
+ Map originalParams = (Map) sa.getReplacingObject(AbilityKey.OriginalParams);
+ Map params = Maps.newHashMap(originalParams);
+
+ String replaced = (String)sa.getReplacingObject(AbilityKey.Mana);
+ if (sa.hasParam("ReplaceMana")) {
+ // replace type and amount
+ replaced = sa.getParam("ReplaceMana");
+ if ("Any".equals(replaced)) {
+ byte rs = MagicColor.GREEN;
+ rs = player.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS);
+ replaced = MagicColor.toShortString(rs);
+ }
+ } else if (sa.hasParam("ReplaceType")) {
+ // replace color and colorless
+ String color = sa.getParam("ReplaceType");
+ if ("Any".equals(color)) {
+ byte rs = MagicColor.GREEN;
+ rs = player.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS);
+ color = MagicColor.toShortString(rs);
+ }
+ for (byte c : MagicColor.WUBRGC) {
+ String s = MagicColor.toShortString(c);
+ replaced = replaced.replace(s, color);
+ }
+ } else if (sa.hasParam("ReplaceColor")) {
+ // replace color
+ String color = sa.getParam("ReplaceColor");
+ if ("Chosen".equals(color)) {
+ if (card.hasChosenColor()) {
+ color = MagicColor.toShortString(card.getChosenColor());
+ }
+ }
+ if (sa.hasParam("ReplaceOnly")) {
+ replaced = replaced.replace(sa.getParam("ReplaceOnly"), color);
+ } else {
+ for (byte c : MagicColor.WUBRG) {
+ String s = MagicColor.toShortString(c);
+ replaced = replaced.replace(s, color);
+ }
+ }
+ } else if (sa.hasParam("ReplaceAmount")) {
+ // replace amount = multiples
+ replaced = StringUtils.repeat(replaced, " ", Integer.valueOf(sa.getParam("ReplaceAmount")));
+ }
+ params.put(AbilityKey.Mana, replaced);
+
+ // need to log Updated events there, or the log is wrong order
+ String message = sa.getReplacementEffect().toString();
+ if ( !StringUtils.isEmpty(message)) {
+ message = TextUtil.fastReplace(message, "CARDNAME", card.getName());
+ game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
+ }
+
+ //try to call replacementHandler with new Params
+ ReplacementResult result = game.getReplacementHandler().run(event, params);
+ switch (result) {
+ case NotReplaced:
+ case Updated: {
+ for (Map.Entry e : params.entrySet()) {
+ originalParams.put(e.getKey(), e.getValue());
+ }
+ // effect was updated
+ originalParams.put(AbilityKey.ReplacementResult, ReplacementResult.Updated);
+ break;
+ }
+ default:
+ // effect was replaced with something else
+ originalParams.put(AbilityKey.ReplacementResult, result);
+ break;
+ }
+ }
+
+}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java
index f06565f1486..ccf1fa909e5 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceSplitDamageEffect.java
@@ -53,7 +53,7 @@ public class ReplaceSplitDamageEffect extends SpellAbilityEffect {
if (card.getType().hasStringType("Effect") && prevent <= 0) {
game.getAction().exile(card, null);
} else if (!StringUtils.isNumeric(varValue)) {
- card.setSVar(varValue, "Number$" + prevent);
+ sa.setSVar(varValue, "Number$" + prevent);
}
Card sourceLKI = (Card) sa.getReplacingObject(AbilityKey.Source);
diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java
index c5e695f178e..ca49ca66424 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -625,10 +625,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
public Card manifest(Player p, SpellAbility sa) {
// Turn Face Down (even if it's DFC).
- ManaCost cost = getState(CardStateName.Original).getManaCost();
-
- boolean isCreature = isCreature();
-
// Sometimes cards are manifested while already being face down
if (!turnFaceDown(true) && !isFaceDown()) {
return null;
@@ -643,11 +639,9 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
setManifested(true);
Card c = game.getAction().moveToPlay(this, p, sa);
-
- // Add manifest demorph static ability for creatures
- if (c.isManifested() && isCreature && !cost.isNoCost()) {
- // Add Manifest to original State
- c.getState(CardStateName.Original).addSpellAbility(CardFactoryUtil.abilityManifestFaceUp(c, cost));
+ if (c.isInPlay()) {
+ c.setManifested(true);
+ c.turnFaceDown(true);
c.updateStateForView();
}
@@ -2491,7 +2485,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
if (ab.getApi() == ApiType.ManaReflected) {
colors.addAll(CardUtil.getReflectableManaColors(ab));
} else {
- colors = CardUtil.canProduce(6, ab.getManaPart(), colors);
+ colors = CardUtil.canProduce(6, ab, colors);
}
}
@@ -2502,7 +2496,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
return true;
}
} else {
- if (mana.getManaPart().canProduce(MagicColor.toShortString(s))) {
+ if (mana.canProduce(MagicColor.toShortString(s))) {
return true;
}
}
@@ -5450,15 +5444,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
// Note: This should only be called after state has been set to CardStateName.FaceDown,
// so the below call should be valid since the state should have been created already.
getState(CardStateName.FaceDown).setImageKey(ImageKeys.getTokenKey(image));
- if (!manifested) {
- // remove Manifest Up abilities from Original State
- CardState original = getState(CardStateName.Original);
- for (SpellAbility sa : original.getNonManaAbilities()) {
- if (sa.isManifestUp()) {
- original.removeSpellAbility(sa);
- }
- }
- }
}
public final boolean isForetold() {
@@ -6090,21 +6075,20 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
public boolean hasETBTrigger(final boolean drawbackOnly) {
for (final Trigger tr : getTriggers()) {
- final Map 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;
}
}
@@ -6332,6 +6316,14 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
}
+ if (isInPlay() && isFaceDown() && isManifested()) {
+ CardState oState = getState(CardStateName.Original);
+ ManaCost cost = oState.getManaCost();
+ if (oState.getType().isCreature()) {
+ abilities.add(CardFactoryUtil.abilityManifestFaceUp(this, cost));
+ }
+ }
+
final Collection toRemove = Lists.newArrayListWithCapacity(abilities.size());
for (final SpellAbility sa : abilities) {
sa.setActivatingPlayer(player);
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 8a21f092a91..0d08fbf7e8b 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactory.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactory.java
@@ -117,10 +117,9 @@ public class CardFactory {
* which wouldn't ordinarily get set during a simple Card.copy() call.
*
* */
- private final static Card copySpellHost(final SpellAbility sourceSA, final SpellAbility targetSA){
+ private final static Card copySpellHost(final SpellAbility sourceSA, final SpellAbility targetSA, Player controller) {
final Card source = sourceSA.getHostCard();
final Card original = targetSA.getHostCard();
- Player controller = sourceSA.getActivatingPlayer();
final Card c = copyCard(original, true);
// change the color of the copy (eg: Fork)
@@ -168,17 +167,15 @@ public class CardFactory {
* @param bCopyDetails
* a boolean.
*/
- public final static SpellAbility copySpellAbilityAndPossiblyHost(final SpellAbility sourceSA, final SpellAbility targetSA) {
- Player controller = sourceSA.getActivatingPlayer();
-
+ public final static SpellAbility copySpellAbilityAndPossiblyHost(final SpellAbility sourceSA, final SpellAbility targetSA, final Player controller) {
//it is only necessary to copy the host card if the SpellAbility is a spell, not an ability
- final Card c = targetSA.isSpell() ? copySpellHost(sourceSA, targetSA) : targetSA.getHostCard();
+ final Card c = targetSA.isSpell() ? copySpellHost(sourceSA, targetSA, controller) : targetSA.getHostCard();
final SpellAbility copySA;
if (targetSA.isTrigger() && targetSA.isWrapper()) {
- copySA = getCopiedTriggeredAbility((WrappedAbility)targetSA, c);
+ copySA = getCopiedTriggeredAbility((WrappedAbility)targetSA, c, controller);
} else {
- copySA = targetSA.copy(c, false);
+ copySA = targetSA.copy(c, controller, false);
}
copySA.setCopied(true);
@@ -361,12 +358,12 @@ public class CardFactory {
// Name first so Senty has the Card name
c.setName(face.getName());
+ for (Entry v : face.getVariables()) c.setSVar(v.getKey(), v.getValue());
+
for (String r : face.getReplacements()) c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true));
for (String s : face.getStaticAbilities()) c.addStaticAbility(s);
for (String t : face.getTriggers()) c.addTrigger(TriggerHandler.parseTrigger(t, c, true));
- for (Entry v : face.getVariables()) c.setSVar(v.getKey(), v.getValue());
-
// keywords not before variables
c.addIntrinsicKeywords(face.getKeywords(), false);
@@ -555,12 +552,12 @@ public class CardFactory {
*
* return a wrapped ability
*/
- public static SpellAbility getCopiedTriggeredAbility(final WrappedAbility sa, final Card newHost) {
+ public static SpellAbility getCopiedTriggeredAbility(final WrappedAbility sa, final Card newHost, final Player controller) {
if (!sa.isTrigger()) {
return null;
}
- return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, false), sa.getDecider());
+ return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, controller, false), controller);
}
public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase sa) {
@@ -667,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);
}
}
@@ -690,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);
}
@@ -703,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);
}
diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
index 75d7c69e7e2..0daf6d6d3ac 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
@@ -177,7 +177,7 @@ public class CardFactoryUtil {
// Cost need to be set later
StringBuilder sb = new StringBuilder();
sb.append("ST$ SetState | Cost$ 0 | CostDesc$ Unmanifest ").append(costDesc);
- sb.append(" | ManifestUp$ True | Secondary$ True | IsPresent$ Card.Self+faceDown+manifested");
+ sb.append(" | ManifestUp$ True | Secondary$ True | PresentDefined$ Self | IsPresent$ Card.faceDown+manifested");
sb.append(" | Mode$ TurnFace | SpellDescription$ (Turn this face up any time for its mana cost.)");
final SpellAbility manifestUp = AbilityFactory.getAbility(sb.toString(), sourceCard);
@@ -1478,7 +1478,7 @@ public class CardFactoryUtil {
for (Card card : otb) {
if (!card.isTapped() || !untappedOnly) {
for (SpellAbility ma : card.getManaAbilities()) {
- if (ma.getManaPart().canProduce(MagicColor.toShortString(color))) {
+ if (ma.canProduce(MagicColor.toShortString(color))) {
uniqueColors++;
continue outer;
}
@@ -2230,7 +2230,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);
diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java
index 369eaab2527..445a3bf6956 100644
--- a/forge-game/src/main/java/forge/game/card/CardProperty.java
+++ b/forge-game/src/main/java/forge/game/card/CardProperty.java
@@ -648,9 +648,10 @@ public class CardProperty {
return false;
}
} else if (property.startsWith("DamagedBy")) {
- if ((property.endsWith("Source") || property.equals("DamagedBy")) &&
- !card.getReceivedDamageFromThisTurn().containsKey(source)) {
- return false;
+ if (property.endsWith("Source") || property.equals("DamagedBy")) {
+ if (!card.getReceivedDamageFromThisTurn().containsKey(source)) {
+ return false;
+ }
} else {
String prop = property.substring("DamagedBy".length());
@@ -911,7 +912,7 @@ public class CardProperty {
} else if (property.startsWith("canProduceManaColor")) {
final String color = property.split("canProduceManaColor ")[1];
for (SpellAbility ma : card.getManaAbilities()) {
- if (ma.getManaPart().canProduce(MagicColor.toShortString(color))) {
+ if (ma.canProduce(MagicColor.toShortString(color))) {
return true;
}
}
diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java
index e1726689621..975c43a62cc 100644
--- a/forge-game/src/main/java/forge/game/card/CardUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardUtil.java
@@ -391,7 +391,6 @@ public final class CardUtil {
final String colorOrType = sa.getParam("ColorOrType");
// currently Color or Type, Type is colors + colorless
- final String validCard = sa.getParam("Valid");
final String reflectProperty = sa.getParam("ReflectProperty");
// Produce (Reflecting Pool) or Is (Meteor Crater)
@@ -400,28 +399,30 @@ public final class CardUtil {
maxChoices++;
}
- CardCollection cards = null;
+ CardCollection cards;
- // Reuse AF_Defined in a slightly different way
- if (validCard.startsWith("Defined.")) {
- cards = AbilityUtils.getDefinedCards(card, TextUtil.fastReplace(validCard, "Defined.", ""), abMana);
- } else {
- if (sa.getActivatingPlayer() == null) {
- sa.setActivatingPlayer(sa.getHostCard().getController());
+ if (sa.hasParam("Valid")) {
+ final String validCard = sa.getParam("Valid");
+ // Reuse AF_Defined in a slightly different way
+ if (validCard.startsWith("Defined.")) {
+ cards = AbilityUtils.getDefinedCards(card, TextUtil.fastReplace(validCard, "Defined.", ""), abMana);
+ } else {
+ if (sa.getActivatingPlayer() == null) {
+ sa.setActivatingPlayer(sa.getHostCard().getController());
+ }
+ final Game game = sa.getActivatingPlayer().getGame();
+ cards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validCard, abMana.getActivatingPlayer(), card);
}
- final Game game = sa.getActivatingPlayer().getGame();
- cards = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), validCard, abMana.getActivatingPlayer(), card);
- }
- // remove anything cards that is already in parents
- for (final Card p : parents) {
- cards.remove(p);
- }
+ // remove anything cards that is already in parents
+ cards.removeAll(parents);
- if ((cards.size() == 0) && !reflectProperty.equals("Produced")) {
- return colors;
+ if (cards.isEmpty()) {
+ return colors;
+ }
+ } else {
+ cards = new CardCollection();
}
-
if (reflectProperty.equals("Is")) { // Meteor Crater
for (final Card card1 : cards) {
// For each card, go through all the colors and if the card is that color, add
@@ -436,7 +437,7 @@ public final class CardUtil {
}
} else if (reflectProperty.equals("Produced")) {
// Why is this name so similar to the one below?
- final String producedColors = abMana instanceof AbilitySub ? (String) abMana.getRootAbility().getTriggeringObject(AbilityKey.Produced) : (String) abMana.getTriggeringObject(AbilityKey.Produced);
+ final String producedColors = (String) abMana.getRootAbility().getTriggeringObject(AbilityKey.Produced);
for (final String col : MagicColor.Constant.ONLY_COLORS) {
final String s = MagicColor.toShortString(col);
if (producedColors.contains(s)) {
@@ -469,7 +470,7 @@ public final class CardUtil {
}
continue;
}
- colors = canProduce(maxChoices, ab.getManaPart(), colors);
+ colors = canProduce(maxChoices, ab, colors);
if (!parents.contains(ab.getHostCard())) {
parents.add(ab.getHostCard());
}
@@ -486,19 +487,18 @@ public final class CardUtil {
return colors;
}
- public static Set canProduce(final int maxChoices, final AbilityManaPart ab,
+ public static Set canProduce(final int maxChoices, final SpellAbility sa,
final Set colors) {
- if (ab == null) {
+ if (sa == null) {
return colors;
}
for (final String col : MagicColor.Constant.ONLY_COLORS) {
- final String s = MagicColor.toShortString(col);
- if (ab.canProduce(s)) {
+ if (sa.canProduce(MagicColor.toShortString(col))) {
colors.add(col);
}
}
- if (maxChoices == 6 && ab.canProduce("C")) {
+ if (maxChoices == 6 && sa.canProduce("C")) {
colors.add(MagicColor.Constant.COLORLESS);
}
diff --git a/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java b/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java
index 6ab3e39c5dd..c46309f38bd 100644
--- a/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java
+++ b/forge-game/src/main/java/forge/game/mana/ManaCostBeingPaid.java
@@ -20,6 +20,8 @@ package forge.game.mana;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.IParserManaCost;
@@ -105,11 +107,16 @@ public class ManaCostBeingPaid {
xCount = copy.xCount;
totalCount = copy.totalCount;
}
+
+ @Override
+ public String toString() {
+ return "{x=" + xCount + " total=" + totalCount + "}";
+ }
}
// holds Mana_Part objects
// ManaPartColor is stored before ManaPartGeneric
- private final Map unpaidShards = new HashMap<>();
+ private final Map unpaidShards = Maps.newHashMap();
private Map xManaCostPaidByColor;
private final String sourceRestriction;
private byte sunburstMap = 0;
@@ -124,7 +131,7 @@ public class ManaCostBeingPaid {
unpaidShards.put(m.getKey(), new ShardCount(m.getValue()));
}
if (manaCostBeingPaid.xManaCostPaidByColor != null) {
- xManaCostPaidByColor = new HashMap<>(manaCostBeingPaid.xManaCostPaidByColor);
+ xManaCostPaidByColor = Maps.newHashMap(manaCostBeingPaid.xManaCostPaidByColor);
}
sourceRestriction = manaCostBeingPaid.sourceRestriction;
sunburstMap = manaCostBeingPaid.sunburstMap;
@@ -503,7 +510,7 @@ public class ManaCostBeingPaid {
sc.xCount--;
String color = MagicColor.toShortString(colorMask);
if (xManaCostPaidByColor == null) {
- xManaCostPaidByColor = new HashMap<>();
+ xManaCostPaidByColor = Maps.newHashMap();
}
Integer xColor = xManaCostPaidByColor.get(color);
if (xColor == null) {
@@ -602,19 +609,7 @@ public class ManaCostBeingPaid {
}
int nGeneric = getGenericManaAmount();
- List shards = new ArrayList<>(unpaidShards.keySet());
-
- // TODO Fix this. Should we really be changing Shards here?
- if (false && pool != null) { //replace shards with generic mana if they can be paid with any color mana
- for (int i = 0; i < shards.size(); i++) {
- ManaCostShard shard = shards.get(i);
- if (shard != ManaCostShard.GENERIC && pool.getPossibleColorUses(shard.getColorMask()) == ManaAtom.ALL_MANA_TYPES) {
- nGeneric += unpaidShards.get(shard).totalCount;
- shards.remove(i);
- i--;
- }
- }
- }
+ List shards = Lists.newArrayList(unpaidShards.keySet());
if (nGeneric > 0) {
if (nGeneric <= 20) {
diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java
index 48cf1e038b4..013ab399c3a 100644
--- a/forge-game/src/main/java/forge/game/mana/ManaPool.java
+++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java
@@ -166,23 +166,27 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
owner.updateManaForView();
}
- private void removeMana(final Mana mana) {
- Collection cm = floatingMana.get(mana.getColor());
- if (cm.remove(mana)) {
+ private boolean removeMana(final Mana mana) {
+ if (floatingMana.remove(mana.getColor(), mana)) {
owner.updateManaForView();
owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Removed, mana));
+ return true;
}
+ return false;
}
public final void payManaFromAbility(final SpellAbility saPaidFor, ManaCostBeingPaid manaCost, final SpellAbility saPayment) {
// Mana restriction must be checked before this method is called
final List paidAbs = saPaidFor.getPayingManaAbilities();
- AbilityManaPart abManaPart = saPayment.getManaPartRecursive();
paidAbs.add(saPayment); // assumes some part on the mana produced by the ability will get used
- for (final Mana mana : abManaPart.getLastManaProduced()) {
- if (tryPayCostWithMana(saPaidFor, manaCost, mana, false)) {
- saPaidFor.getPayingMana().add(0, mana);
+
+ // need to get all mana from all ManaAbilities of the SpellAbility
+ for (AbilityManaPart mp : saPayment.getAllManaParts()) {
+ for (final Mana mana : mp.getLastManaProduced()) {
+ if (tryPayCostWithMana(saPaidFor, manaCost, mana, false)) {
+ saPaidFor.getPayingMana().add(0, mana);
+ }
}
}
}
@@ -216,8 +220,12 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
if (!manaCost.isNeeded(mana, this)) {
return false;
}
+ // only pay mana into manaCost when the Mana could be removed from the Mana pool
+ // if the mana wasn't in the mana pool then something is wrong
+ if (!removeMana(mana)) {
+ return false;
+ }
manaCost.payMana(mana, this);
- removeMana(mana);
return true;
}
diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java
index 6acd4c553a4..0b45d8273f6 100644
--- a/forge-game/src/main/java/forge/game/player/Player.java
+++ b/forge-game/src/main/java/forge/game/player/Player.java
@@ -3539,4 +3539,11 @@ public class Player extends GameEntity implements Comparable {
public void resetCycledThisTurn() {
cycledThisTurn = 0;
}
+
+ public boolean hasUrzaLands() {
+ final CardCollectionView landsControlled = getCardsIn(ZoneType.Battlefield);
+ return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine")))
+ && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Power-Plant")))
+ && Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Tower")));
+ }
}
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 bcada42ecd5..b09df0a2b7b 100644
--- a/forge-game/src/main/java/forge/game/player/PlayerController.java
+++ b/forge-game/src/main/java/forge/game/player/PlayerController.java
@@ -112,7 +112,7 @@ public abstract class PlayerController {
public abstract Integer announceRequirements(SpellAbility ability, String announce);
public abstract CardCollectionView choosePermanentsToSacrifice(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message);
public abstract CardCollectionView choosePermanentsToDestroy(SpellAbility sa, int min, int max, CardCollectionView validTargets, String message);
- public abstract TargetChoices chooseNewTargetsFor(SpellAbility ability);
+ public abstract TargetChoices chooseNewTargetsFor(SpellAbility ability, Predicate filter, boolean optional);
public abstract boolean chooseTargetsFor(SpellAbility currentAbility); // this is bad a function for it assigns targets to sa inside its body
// Specify a target of a spell (Spellskite)
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java b/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java
index 5c95623af22..2c31c859b83 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplaceProduceMana.java
@@ -31,10 +31,10 @@ public class ReplaceProduceMana extends ReplacementEffect {
*/
@Override
public boolean canReplace(Map runParams) {
- //Check for tapping
- if (!hasParam("NoTapCheck")) {
+
+ if (hasParam("ValidAbility")) {
final SpellAbility manaAbility = (SpellAbility) runParams.get(AbilityKey.AbilityMana);
- if (manaAbility == null || !manaAbility.getRootAbility().getPayCosts().hasTapCost()) {
+ if (!matchesValid(manaAbility, getParam("ValidAbility").split(","), getHostCard())) {
return false;
}
}
@@ -43,15 +43,23 @@ public class ReplaceProduceMana extends ReplacementEffect {
String full = getParam("ManaAmount");
String operator = full.substring(0, 2);
String operand = full.substring(2);
+
int intoperand = AbilityUtils.calculateAmount(getHostCard(), operand, this);
+
int manaAmount = StringUtils.countMatches((String) runParams.get(AbilityKey.Mana), " ") + 1;
if (!Expressions.compare(manaAmount, operator, intoperand)) {
return false;
}
}
+ if (hasParam("ValidPlayer")) {
+ if (!matchesValid(runParams.get(AbilityKey.Player), getParam("ValidPlayer").split(","), getHostCard())) {
+ return false;
+ }
+ }
+
if (hasParam("ValidCard")) {
- if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidCard").split(","), this.getHostCard())) {
+ if (!matchesValid(runParams.get(AbilityKey.Affected), getParam("ValidCard").split(","), getHostCard())) {
return false;
}
}
@@ -60,4 +68,7 @@ public class ReplaceProduceMana extends ReplacementEffect {
}
+ public void setReplacingObjects(Map runParams, SpellAbility sa) {
+ sa.setReplacingObject(AbilityKey.Mana, runParams.get(AbilityKey.Mana));
+ }
}
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java
index 06e6bdc95d4..d1aa184e48f 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java
@@ -19,6 +19,7 @@ package forge.game.replacement;
import forge.game.Game;
import forge.game.TriggerReplacementBase;
+import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -271,4 +272,13 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
void setMode(ReplacementType mode) {
this.mode = mode;
}
+
+ public SpellAbility ensureAbility() {
+ SpellAbility sa = getOverridingAbility();
+ if (sa == null && hasParam("ReplaceWith")) {
+ sa = AbilityFactory.getAbility(getHostCard(), getParam("ReplaceWith"));
+ setOverridingAbility(sa);
+ }
+ return sa;
+ }
}
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
index 9842d8ca356..e042aace629 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
@@ -17,9 +17,9 @@
*/
package forge.game.replacement;
-import forge.card.MagicColor;
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;
@@ -259,12 +259,17 @@ public class ReplacementHandler {
chosenRE.setHasRun(false);
hasRun.remove(chosenRE);
chosenRE.setOtherChoices(null);
- String message = chosenRE.getDescription();
- if ( !StringUtils.isEmpty(message))
- if (chosenRE.getHostCard() != null) {
- message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
- }
- game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
+
+ // Updated Replacements need to be logged elsewhere because its otherwise in the wrong order
+ if (res != ReplacementResult.Updated) {
+ String message = chosenRE.getDescription();
+ if ( !StringUtils.isEmpty(message))
+ if (chosenRE.getHostCard() != null) {
+ message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
+ }
+ game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
+ }
+
return res;
}
@@ -344,25 +349,11 @@ public class ReplacementHandler {
Player player = host.getController();
- if (mapParams.containsKey("ManaReplacement")) {
- final SpellAbility manaAb = (SpellAbility) runParams.get(AbilityKey.AbilityMana);
- final Player player1 = (Player) runParams.get(AbilityKey.Player);
- final String rep = (String) runParams.get(AbilityKey.Mana);
- // Replaced mana type
- final Card repHost = host;
- String repType = repHost.getSVar(mapParams.get("ManaReplacement"));
- if (repType.contains("Chosen") && repHost.hasChosenColor()) {
- repType = TextUtil.fastReplace(repType, "Chosen", MagicColor.toShortString(repHost.getChosenColor()));
- }
- manaAb.getManaPart().setManaReplaceType(repType);
- manaAb.getManaPart().produceMana(rep, player1, manaAb);
- } else {
- player.getController().playSpellAbilityNoStack(effectSA, true);
- // if the spellability is a replace effect then its some new logic
- // if ReplacementResult is set in run params use that instead
- if (runParams.containsKey(AbilityKey.ReplacementResult)) {
- return (ReplacementResult) runParams.get(AbilityKey.ReplacementResult);
- }
+ player.getController().playSpellAbilityNoStack(effectSA, true);
+ // if the spellability is a replace effect then its some new logic
+ // if ReplacementResult is set in run params use that instead
+ if (runParams.containsKey(AbilityKey.ReplacementResult)) {
+ return (ReplacementResult) runParams.get(AbilityKey.ReplacementResult);
}
return ReplacementResult.Replaced;
@@ -380,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 parseParams(final String repParse) {
@@ -398,7 +392,7 @@ public class ReplacementHandler {
* The card that hosts the replacement effect
* @return The finished instance
*/
- private static ReplacementEffect parseReplacement(final Map mapParams, final Card host, final boolean intrinsic) {
+ private static ReplacementEffect parseReplacement(final Map 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);
@@ -407,6 +401,10 @@ public class ReplacementHandler {
ret.setActiveZone(EnumSet.copyOf(ZoneType.listValueOf(activeZones)));
}
+ if (mapParams.containsKey("ReplaceWith") && sVarHolder != null) {
+ ret.setOverridingAbility(AbilityFactory.getAbility(host, mapParams.get("ReplaceWith"), sVarHolder));
+ }
+
return ret;
}
}
diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
index c867926a28b..9bb1ff3dd0e 100644
--- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
+++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
@@ -18,18 +18,18 @@
package forge.game.spellability;
import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
+
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCostShard;
import forge.game.Game;
-import forge.game.ability.AbilityFactory;
+import forge.game.GameActionUtil;
import forge.game.ability.AbilityKey;
+import forge.game.ability.ApiType;
import forge.game.card.Card;
-import forge.game.card.CardFactoryUtil;
-import forge.game.card.CounterType;
+import forge.game.card.CardUtil;
import forge.game.mana.Mana;
import forge.game.mana.ManaPool;
import forge.game.player.Player;
@@ -37,15 +37,11 @@ import forge.game.replacement.*;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
-import forge.game.zone.ZoneType;
-import forge.util.Lang;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
*
@@ -62,6 +58,7 @@ public class AbilityManaPart implements java.io.Serializable {
private final String origProduced;
private String lastExpressChoice = "";
private final String manaRestrictions;
+ private String extraManaRestrictions = "";
private final String cannotCounterSpell;
private final String addsKeywords;
private final String addsKeywordsType;
@@ -69,7 +66,6 @@ public class AbilityManaPart implements java.io.Serializable {
private final String addsCounters;
private final String triggersWhenSpent;
private final boolean persistentMana;
- private String manaReplaceType;
private transient List lastManaProduced = Lists.newArrayList();
@@ -99,7 +95,6 @@ public class AbilityManaPart implements java.io.Serializable {
this.addsCounters = params.get("AddsCounters");
this.triggersWhenSpent = params.get("TriggersWhenSpent");
this.persistentMana = (null != params.get("PersistentMana")) && "True".equalsIgnoreCase(params.get("PersistentMana"));
- this.manaReplaceType = params.containsKey("ManaReplaceType") ? params.get("ManaReplaceType") : "";
}
/**
@@ -126,14 +121,26 @@ public class AbilityManaPart implements java.io.Serializable {
public final void produceMana(final String produced, final Player player, SpellAbility sa) {
final Card source = this.getSourceCard();
final ManaPool manaPool = player.getManaPool();
- String afterReplace = applyManaReplacement(sa, produced);
+ String afterReplace = produced;
+
+ SpellAbility root = sa == null ? null : sa.getRootAbility();
+
final Map repParams = AbilityKey.mapFromAffected(source);
- repParams.put(AbilityKey.Mana, produced);
+ repParams.put(AbilityKey.Mana, afterReplace);
repParams.put(AbilityKey.Player, player);
- repParams.put(AbilityKey.AbilityMana, sa);
- if (player.getGame().getReplacementHandler().run(ReplacementType.ProduceMana, repParams) != ReplacementResult.NotReplaced) {
+ repParams.put(AbilityKey.AbilityMana, root);
+ repParams.put(AbilityKey.Activator, root == null ? null : root.getActivatingPlayer());
+
+ switch (player.getGame().getReplacementHandler().run(ReplacementType.ProduceMana, repParams)) {
+ case NotReplaced:
+ break;
+ case Updated:
+ afterReplace = (String) repParams.get(AbilityKey.Mana);
+ break;
+ default:
return;
}
+
//clear lastProduced
this.lastManaProduced.clear();
@@ -159,14 +166,14 @@ public class AbilityManaPart implements java.io.Serializable {
// Run triggers
final Map runParams = AbilityKey.mapFromCard(source);
runParams.put(AbilityKey.Player, player);
- runParams.put(AbilityKey.AbilityMana, sa);
runParams.put(AbilityKey.Produced, afterReplace);
+ runParams.put(AbilityKey.AbilityMana, root);
+ runParams.put(AbilityKey.Activator, root == null ? null : root.getActivatingPlayer());
+
player.getGame().getTriggerHandler().runTrigger(TriggerType.TapsForMana, runParams, false);
- if (source.isLand()) {
- player.setTappedLandForManaThisTurn(true);
+ if (source.isLand() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost() ) {
+ player.setTappedLandForManaThisTurn(true);
}
- // Clear Mana replacement
- this.manaReplaceType = "";
} // end produceMana(String)
/**
@@ -236,61 +243,7 @@ public class AbilityManaPart implements java.io.Serializable {
String[] parse = this.addsCounters.split("_");
// Convert random SVars if there are other cards with this effect
if (c.isValid(parse[0], c.getController(), c, null)) {
- final Game game = this.sourceCard.getGame();
- final Card eff = new Card(game.nextCardId(), game);
- eff.setTimestamp(game.getNextTimestamp());
- eff.setName(sourceCard.getName() + "'s Effect");
- eff.addType("Effect");
- eff.setOwner(controller);
-
- eff.setImageKey(sourceCard.getImageKey());
- eff.setColor(MagicColor.COLORLESS);
- eff.setImmutable(true);
- // try to get the SpellAbility from the mana ability
- //eff.setEffectSource((SpellAbility)null);
-
- eff.addRemembered(c);
-
- String abStr = "DB$ PutCounter | Defined$ ReplacedCard | CounterType$ " + parse[1]
- + " | ETB$ True | CounterNum$ " + parse[2];
-
- SpellAbility sa = AbilityFactory.getAbility(abStr, c);
- if (!StringUtils.isNumeric(parse[2])) {
- sa.setSVar(parse[2], sourceCard.getSVar(parse[2]));
- }
- CardFactoryUtil.setupETBReplacementAbility(sa);
-
- String desc = "It enters the battlefield with ";
- desc += Lang.nounWithNumeral(parse[2], CounterType.getType(parse[1]).getName() + " counter");
- desc += " on it.";
-
- String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | Description$ " + desc;
-
- ReplacementEffect re = ReplacementHandler.parseReplacement(repeffstr, eff, true);
- re.setLayer(ReplacementLayer.Other);
- re.setOverridingAbility(sa);
-
- eff.addReplacementEffect(re);
-
- // Forgot Trigger
- String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ Stack | Destination$ Any | TriggerZones$ Command | Static$ True";
- String forgetEffect = "DB$ Pump | ForgetObjects$ TriggeredCard";
- String exileEffect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"
- + " | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0";
-
- SpellAbility saForget = AbilityFactory.getAbility(forgetEffect, eff);
- AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, eff);
- saForget.setSubAbility(saExile);
-
- final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, eff, true);
- parsedTrigger.setOverridingAbility(saForget);
- eff.addTrigger(parsedTrigger);
- eff.updateStateForView();
-
- // TODO: Add targeting to the effect so it knows who it's dealing with
- game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
- game.getAction().moveTo(ZoneType.Command, eff, null);
- game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
+ GameActionUtil.createETBCountersEffect(sourceCard, c, controller, parse[1], parse[2]);
}
}
@@ -315,7 +268,15 @@ public class AbilityManaPart implements java.io.Serializable {
* @return a {@link java.lang.String} object.
*/
public String getManaRestrictions() {
- return this.manaRestrictions;
+ return manaRestrictions;
+ }
+
+ public void setExtraManaRestriction(String str) {
+ this.extraManaRestrictions = str;
+ }
+
+ public boolean meetsManaRestrictions(final SpellAbility sa) {
+ return meetsManaRestrictions(sa, this.manaRestrictions) && meetsManaRestrictions(sa, this.extraManaRestrictions);
}
/**
@@ -327,14 +288,14 @@ public class AbilityManaPart implements java.io.Serializable {
* a {@link forge.game.spellability.SpellAbility} object.
* @return a boolean.
*/
- public boolean meetsManaRestrictions(final SpellAbility sa) {
+ public boolean meetsManaRestrictions(final SpellAbility sa, String restrictions) {
// No restrictions
- if (this.manaRestrictions.isEmpty()) {
+ if (restrictions.isEmpty()) {
return true;
}
// Loop over restrictions
- for (String restriction : this.manaRestrictions.split(",")) {
+ for (String restriction : restrictions.split(",")) {
if (restriction.equals("nonSpell")) {
return !sa.isSpell();
}
@@ -365,9 +326,9 @@ public class AbilityManaPart implements java.io.Serializable {
if (sa.isValid(restriction, this.getSourceCard().getController(), this.getSourceCard(), null)) {
return true;
}
-
+
if (restriction.equals("CantPayGenericCosts")) {
- return true;
+ return true;
}
if (sa.isAbility()) {
@@ -390,7 +351,7 @@ public class AbilityManaPart implements java.io.Serializable {
return false;
}
-
+
/**
*
* meetsManaShardRestrictions.
@@ -399,44 +360,44 @@ public class AbilityManaPart implements java.io.Serializable {
* @param shard
* a {@link forge.card.mana.ManaCostShard} object.
* @param color
- * the color of mana being paid
+ * the color of mana being paid
* @return a boolean.
*/
public boolean meetsManaShardRestrictions(final ManaCostShard shard, final byte color) {
- if (this.manaRestrictions.isEmpty()) {
+ if (this.manaRestrictions.isEmpty()) {
return true;
}
for (String restriction : this.manaRestrictions.split(",")) {
- if (restriction.equals("CantPayGenericCosts")) {
- if (shard.isGeneric()) {
- if (shard.isOr2Generic() && shard.isColor(color)) {
- continue;
- } else {
- return false;
- }
- } else {
- continue;
- }
- }
+ if (restriction.equals("CantPayGenericCosts")) {
+ if (shard.isGeneric()) {
+ if (shard.isOr2Generic() && shard.isColor(color)) {
+ continue;
+ } else {
+ return false;
+ }
+ } else {
+ continue;
+ }
+ }
}
return true;
}
-
+
/**
*
* meetsSpellAndShardRestrictions.
*
- *
+ *
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param shard
* a {@link forge.card.mana.ManaCostShard} object.
* @param color
- * the color of mana being paid
+ * the color of mana being paid
* @return a boolean.
*/
public boolean meetsSpellAndShardRestrictions(final SpellAbility sa, final ManaCostShard shard, final byte color) {
- return this.meetsManaRestrictions(sa) && this.meetsManaShardRestrictions(shard, color);
+ return this.meetsManaRestrictions(sa) && this.meetsManaShardRestrictions(shard, color);
}
/**
@@ -524,10 +485,6 @@ public class AbilityManaPart implements java.io.Serializable {
return this.getOrigProduced().contains("Special");
}
- public final boolean canProduce(final String s) {
- return canProduce(s, null);
- }
-
/**
*
* canProduce.
@@ -552,24 +509,9 @@ public class AbilityManaPart implements java.io.Serializable {
if (isComboMana()) {
return getComboColors().contains(s);
}
- if (sa != null) {
- return applyManaReplacement(sa, origProduced).contains(s);
- }
return origProduced.contains(s);
}
- /**
- *
- * isBasic.
- *
- *
- * @return a boolean.
- */
- public final boolean isBasic() {
- return this.getOrigProduced().length() == 1 || this.getOrigProduced().contains("Any")
- || this.getOrigProduced().contains("Chosen");
- }
-
/** {@inheritDoc} */
@Override
public final boolean equals(final Object o) {
@@ -645,81 +587,59 @@ public class AbilityManaPart implements java.io.Serializable {
return this.persistentMana;
}
- /**
- * @return the manaReplaceType
- */
- public String getManaReplaceType() {
- return manaReplaceType;
- }
+ boolean abilityProducesManaColor(final SpellAbility am, final byte neededColor) {
+ if (0 != (neededColor & ManaAtom.GENERIC)) {
+ return true;
+ }
- /**
- * setManaReplaceType.
- */
- public void setManaReplaceType(final String type) {
- this.manaReplaceType = type;
- }
- /**
- *
- * applyManaReplacement.
- *
- * @return a String
- */
- public static String applyManaReplacement(final SpellAbility sa, final String original) {
- final Map repMap = Maps.newHashMap();
- final Player act = sa != null ? sa.getActivatingPlayer() : null;
- final String manaReplace = sa != null ? sa.getManaPart().getManaReplaceType(): "";
- if (manaReplace.isEmpty()) {
- if (act != null && act.getLandsPlayedThisTurn() > 0 && sa.hasParam("ReplaceIfLandPlayed")) {
- return sa.getParam("ReplaceIfLandPlayed");
- }
- return original;
+ if (isAnyMana()) {
+ return true;
}
- if (manaReplace.startsWith("Any")) {
- // Replace any type and amount
- String replaced = manaReplace.split("->")[1];
- if (replaced.equals("Any")) {
- byte rs = MagicColor.GREEN;
- if (act != null) {
- rs = act.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS);
+
+ // check for produce mana replacement effects - they mess this up, so just use the mana ability
+ final Card source = am.getHostCard();
+ final Player activator = am.getActivatingPlayer();
+ final Game g = source.getGame();
+ final Map repParams = AbilityKey.newMap();
+ repParams.put(AbilityKey.Mana, getOrigProduced());
+ repParams.put(AbilityKey.Affected, source);
+ repParams.put(AbilityKey.Player, activator);
+ repParams.put(AbilityKey.AbilityMana, am.getRootAbility());
+
+ for (final Player p : g.getPlayers()) {
+ for (final Card crd : p.getAllCards()) {
+ for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
+ if (replacementEffect.requirementsCheck(g)
+ && replacementEffect.getMode() == ReplacementType.ProduceMana
+ && replacementEffect.canReplace(repParams)
+ && replacementEffect.zonesCheck(g.getZoneOf(crd))) {
+ return true;
+ }
}
- replaced = MagicColor.toShortString(rs);
- }
- return replaced;
- }
- final Pattern splitter = Pattern.compile("->");
- // Replace any type
- for (String part : manaReplace.split(" & ")) {
- final String[] v = splitter.split(part, 2);
- // TODO Colorless mana replacement is probably different now?
- if (v[0].equals("Colorless")) {
- repMap.put("[0-9][0-9]?", v.length > 1 ? v[1].trim() : "");
- } else {
- repMap.put(v[0], v.length > 1 ? v[1].trim() : "");
}
}
- // Handle different replacement simultaneously
- Pattern pattern = Pattern.compile(StringUtils.join(repMap.keySet().iterator(), "|"));
- Matcher m = pattern.matcher(original);
- StringBuffer sb = new StringBuffer();
- while(m.find()) {
- if (m.group().matches("[0-9][0-9]?")) {
- final String rep = StringUtils.repeat(repMap.get("[0-9][0-9]?") + " ",
- Integer.parseInt(m.group())).trim();
- m.appendReplacement(sb, rep);
- } else {
- m.appendReplacement(sb, repMap.get(m.group()));
+
+ if (am.getApi() == ApiType.ManaReflected) {
+ final Iterable reflectableColors = CardUtil.getReflectableManaColors(am);
+ for (final String color : reflectableColors) {
+ if (0 != (neededColor & ManaAtom.fromName(color))) {
+ return true;
+ }
}
}
- m.appendTail(sb);
- String replaced = sb.toString();
- while (replaced.contains("Any")) {
- byte rs = MagicColor.GREEN;
- if (act != null) {
- rs = act.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS);
+ else {
+ // treat special mana if it always can be paid
+ if (isSpecialMana()) {
+ return true;
+ }
+ String colorsProduced = isComboMana() ? getComboColors() : mana();
+ for (final String color : colorsProduced.split(" ")) {
+ if (0 != (neededColor & ManaAtom.fromName(color))) {
+ return true;
+ }
}
- replaced = replaced.replaceFirst("Any", MagicColor.toShortString(rs));
}
- return replaced;
+ return false;
}
} // end class AbilityMana
diff --git a/forge-game/src/main/java/forge/game/spellability/AbilitySub.java b/forge-game/src/main/java/forge/game/spellability/AbilitySub.java
index a426e23bf9f..c83e23e714d 100644
--- a/forge-game/src/main/java/forge/game/spellability/AbilitySub.java
+++ b/forge-game/src/main/java/forge/game/spellability/AbilitySub.java
@@ -20,10 +20,6 @@ package forge.game.spellability;
import forge.game.ability.AbilityFactory;
import forge.game.ability.ApiType;
import forge.game.ability.SpellAbilityEffect;
-import forge.game.ability.effects.ChangeZoneAllEffect;
-import forge.game.ability.effects.ChangeZoneEffect;
-import forge.game.ability.effects.ManaEffect;
-import forge.game.ability.effects.ManaReflectedEffect;
import forge.game.card.Card;
import forge.game.cost.Cost;
@@ -92,11 +88,11 @@ public final class AbilitySub extends SpellAbility implements java.io.Serializab
effect = api.getSpellEffect();
- if (effect instanceof ManaEffect || effect instanceof ManaReflectedEffect) {
+ if (api.equals(ApiType.Mana) || api.equals(ApiType.ManaReflected)) {
this.setManaPart(new AbilityManaPart(ca, mapParams));
}
- if (effect instanceof ChangeZoneEffect || effect instanceof ChangeZoneAllEffect) {
+ if (api.equals(ApiType.ChangeZone) || api.equals(ApiType.ChangeZoneAll)) {
AbilityFactory.adjustChangeZoneTarget(mapParams, this);
}
}
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 cd7b9efcc66..6a6f48d53bb 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
@@ -152,6 +152,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private TargetRestrictions targetRestrictions = null;
private TargetChoices targetChosen = new TargetChoices();
+ private Integer dividedValue = null;
+
private SpellAbilityView view;
private StaticAbility mayPlay = null;
@@ -159,6 +161,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private CardCollection lastStateBattlefield = null;
private CardCollection lastStateGraveyard = null;
+ private CardCollection rollbackEffects = new CardCollection();
+
private CardDamageMap damageMap = null;
private CardDamageMap preventMap = null;
private CardZoneTable changeZoneTable = null;
@@ -233,19 +237,101 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
view.updateDescription(this); //description can change if host card does
}
+ public boolean canThisProduce(final String s) {
+ AbilityManaPart mp = getManaPart();
+ if (mp != null && metConditions() && mp.canProduce(s, this)) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean canProduce(final String s) {
+ if (canThisProduce(s)) {
+ return true;
+ }
+
+ return this.subAbility != null ? this.subAbility.canProduce(s) : false;
+ }
+
+ public boolean isManaAbilityFor(SpellAbility saPaidFor, byte colorNeeded) {
+ // is root ability
+ if (this.getParent() == null) {
+ if (!canPlay()) {
+ return false;
+ }
+ if (isAbility() && getRestrictions().isInstantSpeed()) {
+ return false;
+ }
+ }
+
+ AbilityManaPart mp = getManaPart();
+ if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor) && mp.abilityProducesManaColor(this, colorNeeded)) {
+ return true;
+ }
+ return this.subAbility != null ? this.subAbility.isManaAbilityFor(saPaidFor, colorNeeded) : false;
+ }
+
+ public boolean isManaCannotCounter(SpellAbility saPaidFor) {
+ AbilityManaPart mp = getManaPart();
+ if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor) && mp.cannotCounterPaidWith(saPaidFor)) {
+ return true;
+ }
+ return this.subAbility != null ? this.subAbility.isManaCannotCounter(saPaidFor) : false;
+ }
+
+ public int amountOfManaGenerated(boolean multiply) {
+ int result = 0;
+ AbilityManaPart mp = getManaPart();
+ if (mp != null && metConditions()) {
+ int amount = hasParam("Amount") ? AbilityUtils.calculateAmount(getHostCard(), getParam("Amount"), this) : 1;
+
+ if (!multiply || mp.isAnyMana() || mp.isComboMana() || mp.isSpecialMana()) {
+ result += amount;
+ } else {
+ // For cards that produce like {C}{R} vs cards that produce {R}{R}.
+ result += mp.mana().split(" ").length * amount;
+ }
+ }
+ return result;
+ }
+
+ public int totalAmountOfManaGenerated(SpellAbility saPaidFor, boolean multiply) {
+ int result = 0;
+ AbilityManaPart mp = getManaPart();
+ if (mp != null && metConditions() && mp.meetsManaRestrictions(saPaidFor)) {
+ result += amountOfManaGenerated(multiply);
+ }
+ result += subAbility != null ? subAbility.totalAmountOfManaGenerated(saPaidFor, multiply) : 0;
+ return result;
+ }
+
+ public void setManaExpressChoice(ColorSet cs) {
+ AbilityManaPart mp = getManaPart();
+ if (mp != null) {
+ mp.setExpressChoice(cs);
+ }
+ if (subAbility != null) {
+ subAbility.setManaExpressChoice(cs);
+ }
+ }
+
public final AbilityManaPart getManaPart() {
return manaPart;
}
- public final AbilityManaPart getManaPartRecursive() {
- SpellAbility tail = this;
- while (tail != null) {
- if (tail.manaPart != null) {
- return tail.manaPart;
- }
- tail = tail.getSubAbility();
+ public final List getAllManaParts() {
+ AbilityManaPart mp = getManaPart();
+ if (mp == null && subAbility == null) {
+ return ImmutableList.of();
}
- return null;
+ List result = Lists.newArrayList();
+ if (mp != null) {
+ result.add(mp);
+ }
+ if (subAbility != null) {
+ result.addAll(subAbility.getAllManaParts());
+ }
+ return result;
}
public final boolean isManaAbility() {
@@ -262,7 +348,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return false;
}
- return getManaPartRecursive() != null;
+ SpellAbility tail = this;
+ while (tail != null) {
+ if (tail.manaPart != null) {
+ return true;
+ }
+ tail = tail.getSubAbility();
+ }
+ return false;
}
protected final void setManaPart(AbilityManaPart manaPart0) {
@@ -460,6 +553,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
conditions = condition;
}
+ public boolean metConditions() {
+ return getConditions() != null && getConditions().areMet(this);
+ }
+
public List getPayingMana() {
return payingMana;
}
@@ -1101,7 +1198,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (hasParam("TargetingPlayerControls") && entity instanceof Card) {
final Card c = (Card) entity;
- if (!c.getController().equals(targetingPlayer)) {
+ if (!c.getController().equals(getTargetingPlayer())) {
return false;
}
}
@@ -1421,6 +1518,35 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
targetChosen = new TargetChoices();
}
+ /**
+ * @return a boolean dividedAsYouChoose
+ */
+ public boolean isDividedAsYouChoose() {
+ return hasParam("DividedAsYouChoose");
+ }
+
+ public final void addDividedAllocation(final GameObject tgt, final Integer portionAllocated) {
+ getTargets().addDividedAllocation(tgt, portionAllocated);
+ }
+ public Integer getDividedValue(GameObject c) {
+ return getTargets().getDividedValue(c);
+ }
+
+ public int getTotalDividedValue() {
+ return getTargets().getTotalDividedValue();
+ }
+
+ public Integer getDividedValue() {
+ return this.dividedValue;
+ }
+
+ public int getStillToDivide() {
+ if (!isDividedAsYouChoose() || dividedValue == null) {
+ return 0;
+ }
+ return dividedValue - getTotalDividedValue();
+ }
+
/**
* Reset the first target.
*
@@ -1428,16 +1554,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public void resetFirstTarget(GameObject c, SpellAbility originalSA) {
SpellAbility sa = this;
while (sa != null) {
- if (sa.targetRestrictions != null) {
- sa.targetChosen = new TargetChoices();
- sa.targetChosen.add(c);
- if (!originalSA.targetRestrictions.getDividedMap().isEmpty()) {
- sa.targetRestrictions.addDividedAllocation(c,
- Iterables.getFirst(originalSA.targetRestrictions.getDividedMap().values(), null));
+ if (sa.usesTargeting()) {
+ sa.resetTargets();
+ sa.getTargets().add(c);
+ if (!originalSA.getTargets().getDividedValues().isEmpty()) {
+ sa.addDividedAllocation(c, Iterables.getFirst(originalSA.getTargets().getDividedValues(), null));
}
break;
}
- sa = sa.subAbility;
+ sa = sa.getSubAbility();
}
}
@@ -1454,7 +1579,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
}
public boolean isZeroTargets() {
- return getTargetRestrictions().getMinTargets(hostCard, this) == 0 && getTargets().size() == 0;
+ return getMinTargets() == 0 && getTargets().size() == 0;
}
public boolean isMinTargetChosen() {
@@ -1728,6 +1853,27 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return p != null && p.isTargeting(o);
}
+ public boolean setupNewTargets(Player forceTargetingPlayer) {
+ // Skip to paying if parent ability doesn't target and has no subAbilities.
+ // (or trigger case where its already targeted)
+ SpellAbility currentAbility = this;
+ do {
+ if (currentAbility.usesTargeting()) {
+ TargetChoices oldTargets = currentAbility.getTargets();
+ if (forceTargetingPlayer.getController().chooseNewTargetsFor(currentAbility, null, true) == null) {
+ currentAbility.setTargets(oldTargets);
+ }
+ }
+ final AbilitySub subAbility = currentAbility.getSubAbility();
+ if (subAbility != null) {
+ // This is necessary for "TargetsWithDefinedController$ ParentTarget"
+ subAbility.setParent(currentAbility);
+ }
+ currentAbility = subAbility;
+ } while (currentAbility != null);
+ return true;
+ }
+
public boolean setupTargets() {
// Skip to paying if parent ability doesn't target and has no subAbilities.
// (or trigger case where its already targeted)
@@ -1745,6 +1891,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} else {
targetingPlayer = getActivatingPlayer();
}
+ // don't set targeting player when forceful target,
+ // "targeting player controls" should not be reset when the spell is copied
currentAbility.setTargetingPlayer(targetingPlayer);
if (!targetingPlayer.getController().chooseTargetsFor(currentAbility)) {
return false;
@@ -1760,11 +1908,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return true;
}
public final void clearTargets() {
- final TargetRestrictions tg = getTargetRestrictions();
- if (tg != null) {
+ if (usesTargeting()) {
resetTargets();
- if (hasParam("DividedAsYouChoose")) {
- tg.calculateStillToDivide(getParam("DividedAsYouChoose"), getHostCard(), this);
+ if (isDividedAsYouChoose()) {
+ this.dividedValue = AbilityUtils.calculateAmount(getHostCard(), this.getParam("DividedAsYouChoose"), this);
}
}
}
@@ -1830,9 +1977,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public boolean tracksManaSpent() {
if (hostCard == null || hostCard.getRules() == null) { return false; }
- if (hostCard.hasKeyword(Keyword.SUNBURST)) {
+ if (isSpell() && hostCard.hasConverge()) {
return true;
}
+
String text = hostCard.getRules().getOracleText();
if (isSpell() && text.contains("was spent to cast")) {
return true;
@@ -1969,7 +2117,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
String mana = manaPart.mana();
if (!mana.equals("Any")) {
score += mana.length();
- if (!manaPart.canProduce("C")) {
+ if (!canProduce("C")) {
// Producing colorless should produce a slightly lower score
score += 1;
}
@@ -2134,4 +2282,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public boolean checkRestrictions(Card host, Player activator) {
return true;
}
+
+ public void addRollbackEffect(Card eff) {
+ rollbackEffects.add(eff);
+ }
+
+ public void rollback() {
+ for (Card c : rollbackEffects) {
+ c.ceaseToExist();
+ }
+ rollbackEffects.clear();
+ }
}
diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java
index ee737cb3b05..14106f5c977 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java
@@ -32,11 +32,10 @@ import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
import forge.game.GameObject;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -85,7 +84,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
private Integer xManaPaid = null;
// Other Paid things
- private final HashMap paidHash;
+ private final Map paidHash;
// Additional info
// is Kicked, is Buyback
@@ -96,7 +95,6 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
private final Map storedSVars = Maps.newHashMap();
- private final List zonesToOpen;
private final Map playersWithValidTargets;
private final StackItemView view;
@@ -109,7 +107,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
activatingPlayer = sa.getActivatingPlayer();
// Payment info
- paidHash = new HashMap<>(ability.getPaidHash());
+ paidHash = Maps.newHashMap(ability.getPaidHash());
ability.resetPaidHash();
splicedCards = sa.getSplicedCards();
@@ -149,18 +147,13 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
//store zones to open and players to open them for at the time the SpellAbility first goes on the stack based on the selected targets
if (tc == null) {
- zonesToOpen = null;
playersWithValidTargets = null;
}
else {
- zonesToOpen = new ArrayList<>();
- playersWithValidTargets = new HashMap<>();
+ playersWithValidTargets = Maps.newHashMap();
for (Card card : tc.getTargetCards()) {
ZoneType zoneType = card.getZone() != null ? card.getZone().getZoneType() : null;
if (zoneType != ZoneType.Battlefield) { //don't need to worry about targets on battlefield
- if (zoneType != null && !zonesToOpen.contains(zoneType)) {
- zonesToOpen.add(zoneType);
- }
playersWithValidTargets.put(card.getController(), null);
}
}
@@ -253,75 +246,32 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
return tc;
}
- public final List getZonesToOpen() {
- return zonesToOpen;
- }
-
public final Map getPlayersWithValidTargets() {
return playersWithValidTargets;
}
public void updateTarget(TargetChoices target) {
- updateTarget(target, null, null);
- }
-
- public void updateTarget(TargetChoices target, GameObject oldTarget, GameObject newTarget) {
if (target != null) {
+ TargetChoices oldTarget = tc;
tc = target;
ability.setTargets(tc);
stackDescription = ability.getStackDescription();
view.updateTargetCards(this);
view.updateTargetPlayers(this);
view.updateText(this);
-
- if (ability.hasParam("DividedAsYouChoose")) {
- // try to update DividedAsYouChoose after retargeting
- Object toRemove = null;
- Object toAdd = null;
- HashMap