diff --git a/forge-ai/pom.xml b/forge-ai/pom.xml
index 3f176653a65..667324e060d 100644
--- a/forge-ai/pom.xml
+++ b/forge-ai/pom.xml
@@ -6,7 +6,7 @@
forgeforge
- 1.6.33-SNAPSHOT
+ 1.6.36-SNAPSHOTforge-ai
diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java
index 51798704399..b3dc45e4999 100644
--- a/forge-ai/src/main/java/forge/ai/AiAttackController.java
+++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java
@@ -24,6 +24,7 @@ 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;
import forge.game.card.*;
@@ -464,7 +465,7 @@ public class AiAttackController {
final CardCollectionView beastions = ai.getCardsIn(ZoneType.Battlefield, "Beastmaster Ascension");
int minCreatures = 7;
for (final Card beastion : beastions) {
- final int counters = beastion.getCounters(CounterType.QUEST);
+ final int counters = beastion.getCounters(CounterEnumType.QUEST);
minCreatures = Math.min(minCreatures, 7 - counters);
}
if (this.attackers.size() >= minCreatures) {
@@ -1065,7 +1066,7 @@ public class AiAttackController {
}
}
// if enough damage: switch to next planeswalker or player
- if (damage >= pw.getCounters(CounterType.LOYALTY)) {
+ if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
List pwDefending = combat.getDefendingPlaneswalkers();
boolean found = false;
// look for next planeswalker
@@ -1135,7 +1136,6 @@ public class AiAttackController {
// TODO Somehow subtract expected damage of other attacking creatures from enemy life total (how? other attackers not yet declared? Can the AI guesstimate which of their creatures will not get blocked?)
if (attacker.getCurrentPower() * Integer.parseInt(attacker.getSVar("NonCombatPriority")) < ai.getOpponentsSmallestLifeTotal()) {
// Check if the card actually has an ability the AI can and wants to play, if not, attacking is fine!
- boolean wantability = false;
for (SpellAbility sa : attacker.getSpellAbilities()) {
// Do not attack if we can afford using the ability.
if (sa.isAbility()) {
@@ -1192,7 +1192,7 @@ public class AiAttackController {
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
numberOfPossibleBlockers += 1;
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
- && !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.P1P1) == 0)) {
+ && !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.P1P1) == 0)) {
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
// see if the defending creature is of higher or lower
// value. We don't want to attack only to lose value
@@ -1365,21 +1365,12 @@ public class AiAttackController {
if (c.hasSVar("AIExertCondition")) {
if (!c.getSVar("AIExertCondition").isEmpty()) {
final String needsToExert = c.getSVar("AIExertCondition");
- int x = 0;
- int y = 0;
String sVar = needsToExert.split(" ")[0];
String comparator = needsToExert.split(" ")[1];
String compareTo = comparator.substring(2);
- try {
- x = Integer.parseInt(sVar);
- } catch (final NumberFormatException e) {
- x = CardFactoryUtil.xCount(c, c.getSVar(sVar));
- }
- try {
- y = Integer.parseInt(compareTo);
- } catch (final NumberFormatException e) {
- y = CardFactoryUtil.xCount(c, c.getSVar(compareTo));
- }
+
+ int x = AbilityUtils.calculateAmount(c, sVar, null);
+ int y = AbilityUtils.calculateAmount(c, compareTo, null);
if (Expressions.compare(x, comparator, y)) {
shouldExert = true;
}
diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java
index bef6eaa8961..7f1104d95fd 100644
--- a/forge-ai/src/main/java/forge/ai/AiBlockController.java
+++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java
@@ -228,9 +228,9 @@ public class AiBlockController {
// 3.Blockers that can destroy the attacker and have an upside when dying
killingBlockers = getKillingBlockers(combat, attacker, blockers);
for (Card b : killingBlockers) {
- if ((b.hasKeyword(Keyword.UNDYING) && b.getCounters(CounterType.P1P1) == 0) || b.hasSVar("SacMe")
- || (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
- || (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
+ if ((b.hasKeyword(Keyword.UNDYING) && b.getCounters(CounterEnumType.P1P1) == 0) || b.hasSVar("SacMe")
+ || (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterEnumType.TIME) == 1)
+ || (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterEnumType.FADE) == 0)
|| b.hasSVar("EndOfTurnLeavePlay")) {
blocker = b;
break;
@@ -299,8 +299,8 @@ public class AiBlockController {
final List blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
for (Card b : blockers) {
- if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
- || (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
+ if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterEnumType.TIME) == 1)
+ || (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterEnumType.FADE) == 0)
|| b.hasSVar("EndOfTurnLeavePlay")) {
blocker = b;
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
@@ -851,7 +851,7 @@ public class AiBlockController {
damageToPW += ComputerUtilCombat.predictDamageTo((Card) def, pwatkr.getNetCombatDamage(), pwatkr, true);
}
}
- if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= def.getCounters(CounterType.LOYALTY)) {
+ if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= def.getCounters(CounterEnumType.LOYALTY)) {
threatenedPWs.add((Card) def);
}
}
@@ -909,7 +909,7 @@ public class AiBlockController {
damageToPW += ComputerUtilCombat.predictDamageTo(pw, pwAtk.getNetCombatDamage(), pwAtk, true);
}
}
- if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterType.LOYALTY)) {
+ if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterEnumType.LOYALTY)) {
for (Card chump : pwDefenders) {
if (chosenChumpBlockers.contains(chump)) {
combat.removeFromCombat(chump);
diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java
index b4b3293d108..8f941ed6886 100644
--- a/forge-ai/src/main/java/forge/ai/AiController.java
+++ b/forge-ai/src/main/java/forge/ai/AiController.java
@@ -177,7 +177,7 @@ public class AiController {
&& CardFactoryUtil.isCounterable(host)) {
return true;
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
- && host.getCMC() == c.getCounters(CounterType.CHARGE)) {
+ && host.getCMC() == c.getCounters(CounterEnumType.CHARGE)) {
return true;
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
String hostName = host.getName();
@@ -769,7 +769,7 @@ public class AiController {
return AiPlayDecision.CantPlayAi;
}
}
- else if (sa.getPayCosts() != null){
+ else {
Cost payCosts = sa.getPayCosts();
ManaCost mana = payCosts.getTotalMana();
if (mana != null) {
@@ -858,7 +858,7 @@ public class AiController {
int neededMana = 0;
boolean dangerousRecurringCost = false;
- Cost costWithBuyback = sa.getPayCosts() != null ? sa.getPayCosts().copy() : Cost.Zero;
+ Cost costWithBuyback = sa.getPayCosts().copy();
for (OptionalCostValue opt : GameActionUtil.getOptionalCostValues(sa)) {
if (opt.getType() == OptionalCost.Buyback) {
costWithBuyback.add(opt.getCost());
@@ -907,8 +907,8 @@ public class AiController {
public int compare(final SpellAbility a, final SpellAbility b) {
// sort from highest cost to lowest
// we want the highest costs first
- int a1 = a.getPayCosts() == null ? 0 : a.getPayCosts().getTotalMana().getCMC();
- int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC();
+ int a1 = a.getPayCosts().getTotalMana().getCMC();
+ int b1 = b.getPayCosts().getTotalMana().getCMC();
// deprioritize SAs explicitly marked as preferred to be activated last compared to all other SAs
if (a.hasParam("AIActivateLast") && !b.hasParam("AIActivateLast")) {
@@ -927,12 +927,12 @@ public class AiController {
// deprioritize pump spells with pure energy cost (can be activated last,
// since energy is generally scarce, plus can benefit e.g. Electrostatic Pummeler)
int a2 = 0, b2 = 0;
- if (a.getApi() == ApiType.Pump && a.getPayCosts() != null && a.getPayCosts().getCostEnergy() != null) {
+ if (a.getApi() == ApiType.Pump && a.getPayCosts().getCostEnergy() != null) {
if (a.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
a2 = a.getPayCosts().getCostEnergy().convertAmount();
}
}
- if (b.getApi() == ApiType.Pump && b.getPayCosts() != null && b.getPayCosts().getCostEnergy() != null) {
+ if (b.getApi() == ApiType.Pump && b.getPayCosts().getCostEnergy() != null) {
if (b.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
b2 = b.getPayCosts().getCostEnergy().convertAmount();
}
@@ -956,8 +956,7 @@ public class AiController {
return 1;
}
- if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()
- && a.getPayCosts() != null && b.getPayCosts() != null) {
+ if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()) {
// Cheaper Spectacle costs should be preferred
// FIXME: Any better way to identify that these are the same ability, one with Spectacle and one not?
// (looks like it's not a full-fledged alternative cost as such, and is not processed with other alt costs)
@@ -1479,7 +1478,7 @@ public class AiController {
}
for (SpellAbility sa : card.getSpellAbilities()) {
- if (sa.getPayCosts() != null && sa.isAbility()
+ if (sa.isAbility()
&& sa.getPayCosts().getCostMana() != null
&& sa.getPayCosts().getCostMana().getMana().getCMC() > 0
&& (!sa.getPayCosts().hasTapCost() || !isTapLand)
@@ -1802,7 +1801,7 @@ public class AiController {
throw new UnsupportedOperationException("AI is not supposed to reach this code at the moment");
}
- public CardCollection chooseCardsForEffect(CardCollectionView pool, SpellAbility sa, int min, int max, boolean isOptional) {
+ public CardCollection chooseCardsForEffect(CardCollectionView pool, SpellAbility sa, int min, int max, boolean isOptional, Map params) {
if (sa == null || sa.getApi() == null) {
throw new UnsupportedOperationException();
}
@@ -1835,7 +1834,7 @@ public class AiController {
default:
CardCollection editablePool = new CardCollection(pool);
for (int i = 0; i < max; i++) {
- Card c = player.getController().chooseSingleEntityForEffect(editablePool, sa, null, isOptional);
+ Card c = player.getController().chooseSingleEntityForEffect(editablePool, sa, null, isOptional, params);
if (c != null) {
result.add(c);
editablePool.remove(c);
@@ -1986,6 +1985,35 @@ public class AiController {
return MyRandom.getRandom().nextBoolean();
}
+ public boolean chooseEvenOdd(SpellAbility sa) {
+ String aiLogic = sa.getParamOrDefault("AILogic", "");
+
+ if (aiLogic.equals("AlwaysEven")) {
+ return false; // false is Even
+ } else if (aiLogic.equals("AlwaysOdd")) {
+ return true; // true is Odd
+ } else if (aiLogic.equals("Random")) {
+ return MyRandom.getRandom().nextBoolean();
+ } else if (aiLogic.equals("CMCInHand")) {
+ CardCollectionView hand = sa.getActivatingPlayer().getCardsIn(ZoneType.Hand);
+ int numEven = CardLists.filter(hand, CardPredicates.evenCMC()).size();
+ int numOdd = CardLists.filter(hand, CardPredicates.oddCMC()).size();
+ return numOdd > numEven;
+ } else if (aiLogic.equals("CMCOppControls")) {
+ CardCollectionView hand = sa.getActivatingPlayer().getOpponents().getCardsIn(ZoneType.Battlefield);
+ int numEven = CardLists.filter(hand, CardPredicates.evenCMC()).size();
+ int numOdd = CardLists.filter(hand, CardPredicates.oddCMC()).size();
+ return numOdd > numEven;
+ } else if (aiLogic.equals("CMCOppControlsByPower")) {
+ // TODO: improve this to check for how dangerous those creatures actually are relative to host card
+ CardCollectionView hand = sa.getActivatingPlayer().getOpponents().getCardsIn(ZoneType.Battlefield);
+ int powerEven = Aggregates.sum(CardLists.filter(hand, CardPredicates.evenCMC()), Accessors.fnGetNetPower);
+ int powerOdd = Aggregates.sum(CardLists.filter(hand, CardPredicates.oddCMC()), Accessors.fnGetNetPower);
+ return powerOdd > powerEven;
+ }
+ return MyRandom.getRandom().nextBoolean(); // outside of any specific logic, choose randomly
+ }
+
public Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List origin, SpellAbility sa,
CardCollection fetchList, Player player2, Player decider) {
if (useSimulation) {
diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java
index aa6f83699ca..52abcc6f94b 100644
--- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java
+++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java
@@ -5,6 +5,7 @@ import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import forge.card.CardType;
@@ -16,12 +17,14 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
+import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.cost.*;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.zone.ZoneType;
+import forge.util.Aggregates;
import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
@@ -104,6 +107,24 @@ public class AiCostDecision extends CostDecisionMakerBase {
}
return PaymentDecision.card(randomSubset);
}
+ else if (type.equals("DifferentNames")) {
+ CardCollection differentNames = new CardCollection();
+ CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
+ while (c > 0) {
+ Card chosen;
+ if (!discardMe.isEmpty()) {
+ chosen = Aggregates.random(discardMe);
+ discardMe = CardLists.filter(discardMe, Predicates.not(CardPredicates.sharesNameWith(chosen)));
+ } else {
+ final Card worst = ComputerUtilCard.getWorstAI(hand);
+ chosen = worst != null ? worst : Aggregates.random(hand);
+ }
+ differentNames.add(chosen);
+ hand = CardLists.filter(hand, Predicates.not(CardPredicates.sharesNameWith(chosen)));
+ c--;
+ }
+ return PaymentDecision.card(differentNames);
+ }
else {
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
@@ -329,7 +350,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
}
CardCollectionView topLib = player.getCardsIn(ZoneType.Library, c);
- return topLib.size() < c ? null : PaymentDecision.card(topLib);
+ return topLib.size() < c ? null : PaymentDecision.number(c);
}
@Override
@@ -494,7 +515,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public boolean apply(Card card) {
for (final SpellAbility sa : card.getSpellAbilities()) {
- if (sa.isManaAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
+ if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}
@@ -627,41 +648,41 @@ public class AiCostDecision extends CostDecisionMakerBase {
// the first things are benefit from removing counters
// try to remove +1/+1 counter from undying creature
- List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.P1P1, c),
+ List prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1, c),
CardPredicates.hasKeyword("Undying"));
if (!prefs.isEmpty()) {
- Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.P1P1));
+ Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.P1P1));
PaymentDecision result = PaymentDecision.card(prefs);
- result.ct = CounterType.P1P1;
+ result.ct = CounterType.get(CounterEnumType.P1P1);
return result;
}
// try to remove -1/-1 counter from persist creature
- prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.M1M1, c),
+ prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1, c),
CardPredicates.hasKeyword("Persist"));
if (!prefs.isEmpty()) {
- Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.M1M1));
+ Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.M1M1));
PaymentDecision result = PaymentDecision.card(prefs);
- result.ct = CounterType.M1M1;
+ result.ct = CounterType.get(CounterEnumType.M1M1);
return result;
}
// try to remove Time counter from Chronozoa, it will generate more
- prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.TIME, c),
+ prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME, c),
CardPredicates.nameEquals("Chronozoa"));
if (!prefs.isEmpty()) {
- Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.TIME));
+ Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.TIME));
PaymentDecision result = PaymentDecision.card(prefs);
- result.ct = CounterType.TIME;
+ result.ct = CounterType.get(CounterEnumType.TIME);
return result;
}
// try to remove Quest counter on something with enough counters for the
// effect to continue
- prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.QUEST, c));
+ prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.QUEST, c));
if (!prefs.isEmpty()) {
prefs = CardLists.filter(prefs, new Predicate() {
@@ -673,12 +694,12 @@ public class AiCostDecision extends CostDecisionMakerBase {
if (crd.hasSVar("MaxQuestEffect")) {
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
}
- return crd.getCounters(CounterType.QUEST) >= e + c;
+ return crd.getCounters(CounterEnumType.QUEST) >= e + c;
}
});
- Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterType.QUEST)));
+ Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST)));
PaymentDecision result = PaymentDecision.card(prefs);
- result.ct = CounterType.QUEST;
+ result.ct = CounterType.get(CounterEnumType.QUEST);
return result;
}
@@ -775,7 +796,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
@Override
public boolean apply(final Card crd) {
for (Map.Entry e : crd.getCounters().entrySet()) {
- if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey() == CounterType.valueOf(ctr))) {
+ if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) {
return true;
}
}
@@ -787,7 +808,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
PaymentDecision result = PaymentDecision.card(card);
for (Map.Entry e : card.getCounters().entrySet()) {
- if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey() == CounterType.valueOf(ctr))) {
+ if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) {
result.ct = e.getKey();
break;
}
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
index ff73d875785..5cd886aaf7f 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
@@ -65,7 +65,7 @@ import java.util.*;
*
* ComputerUtil class.
*
- *
+ *
* @author Forge
* @version $Id$
*/
@@ -91,9 +91,6 @@ public class ComputerUtil {
}
}
- source.setCastSA(sa);
- sa.setLastStateBattlefield(game.getLastStateBattlefield());
- sa.setLastStateGraveyard(game.getLastStateGraveyard());
sa.setHostCard(game.getAction().moveToStack(source, sa));
}
@@ -216,12 +213,9 @@ public class ComputerUtil {
sa.setActivatingPlayer(ai);
if (!ComputerUtilCost.canPayCost(sa, ai))
return false;
-
+
final Card source = sa.getHostCard();
if (sa.isSpell() && !source.isCopiedSpell()) {
- source.setCastSA(sa);
- sa.setLastStateBattlefield(game.getLastStateBattlefield());
- sa.setLastStateGraveyard(game.getLastStateGraveyard());
sa.setHostCard(game.getAction().moveToStack(source, sa));
}
@@ -246,9 +240,6 @@ public class ComputerUtil {
final Card source = sa.getHostCard();
if (sa.isSpell() && !source.isCopiedSpell()) {
- source.setCastSA(sa);
- sa.setLastStateBattlefield(game.getLastStateBattlefield());
- sa.setLastStateGraveyard(game.getLastStateGraveyard());
sa.setHostCard(game.getAction().moveToStack(source, sa));
}
@@ -267,9 +258,6 @@ public class ComputerUtil {
final Card source = newSA.getHostCard();
if (newSA.isSpell() && !source.isCopiedSpell()) {
- source.setCastSA(newSA);
- sa.setLastStateBattlefield(game.getLastStateBattlefield());
- sa.setLastStateGraveyard(game.getLastStateGraveyard());
newSA.setHostCard(game.getAction().moveToStack(source, sa));
if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) {
@@ -290,9 +278,6 @@ public class ComputerUtil {
if (ComputerUtilCost.canPayCost(sa, ai)) {
final Card source = sa.getHostCard();
if (sa.isSpell() && !source.isCopiedSpell()) {
- source.setCastSA(sa);
- sa.setLastStateBattlefield(game.getLastStateBattlefield());
- sa.setLastStateGraveyard(game.getLastStateGraveyard());
sa.setHostCard(game.getAction().moveToStack(source, sa));
}
@@ -364,8 +349,8 @@ public class ComputerUtil {
for (int ip = 0; ip < 6; ip++) {
final int priority = 6 - ip;
if (priority == 2 && ai.isCardInPlay("Crucible of Worlds")) {
- CardCollection landsInPlay = CardLists.getType(typeList, "Land");
- if (!landsInPlay.isEmpty()) {
+ CardCollection landsInPlay = CardLists.getType(typeList, "Land");
+ if (!landsInPlay.isEmpty()) {
// Don't need more land.
return ComputerUtilCard.getWorstLand(landsInPlay);
}
@@ -394,16 +379,16 @@ public class ComputerUtil {
return ComputerUtilCard.getWorstLand(landsInPlay);
}
}
-
+
// try everything when about to die
- if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
- && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
- final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature");
- if (!nonCreatures.isEmpty()) {
- return ComputerUtilCard.getWorstAI(nonCreatures);
- } else if (!typeList.isEmpty()) {
- return ComputerUtilCard.getWorstAI(typeList);
- }
+ if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
+ && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
+ final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature");
+ if (!nonCreatures.isEmpty()) {
+ return ComputerUtilCard.getWorstAI(nonCreatures);
+ } else if (!typeList.isEmpty()) {
+ return ComputerUtilCard.getWorstAI(typeList);
+ }
}
}
else if (pref.contains("DiscardCost")) { // search for permanents with DiscardMe
@@ -465,14 +450,14 @@ public class ComputerUtil {
return ComputerUtilCard.getWorstLand(landsInHand);
}
}
-
+
// try everything when about to die
if (activate != null && "Reality Smasher".equals(activate.getName()) ||
- game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
- && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
- if (!typeList.isEmpty()) {
- return ComputerUtilCard.getWorstAI(typeList);
- }
+ game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
+ && ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
+ if (!typeList.isEmpty()) {
+ return ComputerUtilCard.getWorstAI(typeList);
+ }
}
} else if (pref.contains("DonateMe")) {
// search for permanents with DonateMe. priority 1 is the lowest, priority 5 the highest
@@ -555,7 +540,7 @@ public class ComputerUtil {
public static CardCollection chooseExileFrom(final Player ai, final ZoneType zone, final String type, final Card activate,
final Card target, final int amount) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, null);
-
+
if ((target != null) && target.getController() == ai) {
typeList.remove(target); // don't exile the card we're pumping
}
@@ -576,7 +561,7 @@ public class ComputerUtil {
public static CardCollection choosePutToLibraryFrom(final Player ai, final ZoneType zone, final String type, final Card activate,
final Card target, final int amount) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, null);
-
+
if ((target != null) && target.getController() == ai) {
typeList.remove(target); // don't move the card we're pumping
}
@@ -587,11 +572,11 @@ public class ComputerUtil {
CardLists.sortByPowerAsc(typeList);
final CardCollection list = new CardCollection();
-
+
if (zone != ZoneType.Hand) {
Collections.reverse(typeList);
}
-
+
for (int i = 0; i < amount; i++) {
list.add(typeList.get(i));
}
@@ -651,7 +636,7 @@ public class ComputerUtil {
}
ComputerUtilCard.sortByEvaluateCreature(typeList);
Collections.reverse(typeList);
-
+
final CardCollection tapList = new CardCollection();
// Accumulate from "worst" creature
@@ -724,7 +709,7 @@ public class ComputerUtil {
return returnList;
}
- public static CardCollection choosePermanentsToSacrifice(final Player ai, final CardCollectionView cardlist, final int amount, final SpellAbility source,
+ public static CardCollection choosePermanentsToSacrifice(final Player ai, final CardCollectionView cardlist, final int amount, final SpellAbility source,
final boolean destroy, final boolean isOptional) {
CardCollection remaining = new CardCollection(cardlist);
final CardCollection sacrificed = new CardCollection();
@@ -733,9 +718,9 @@ public class ComputerUtil {
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold");
if ("OpponentOnly".equals(source.getParam("AILogic"))) {
- if(!source.getActivatingPlayer().isOpponentOf(ai)) {
- return sacrificed; // sacrifice none
- }
+ if(!source.getActivatingPlayer().isOpponentOf(ai)) {
+ return sacrificed; // sacrifice none
+ }
} else if ("DesecrationDemon".equals(source.getParam("AILogic"))) {
if (!SpecialCardAi.DesecrationDemon.considerSacrificingCreature(ai, source)) {
return sacrificed; // don't sacrifice unless in special conditions specified by DesecrationDemon AI
@@ -753,27 +738,27 @@ public class ComputerUtil {
boolean removedSelf = false;
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit") || considerSacLogic) {
- if (source.hasParam("Exploit")) {
- for (Trigger t : host.getTriggers()) {
- if (t.getMode() == TriggerType.Exploited) {
- final String execute = t.getParam("Execute");
- if (execute == null) {
- continue;
- }
- final SpellAbility exSA = AbilityFactory.getAbility(host.getSVar(execute), host);
+ if (source.hasParam("Exploit")) {
+ for (Trigger t : host.getTriggers()) {
+ if (t.getMode() == TriggerType.Exploited) {
+ final String execute = t.getParam("Execute");
+ if (execute == null) {
+ continue;
+ }
+ final SpellAbility exSA = AbilityFactory.getAbility(host.getSVar(execute), host);
- exSA.setActivatingPlayer(ai);
- exSA.setTrigger(true);
+ exSA.setActivatingPlayer(ai);
+ exSA.setTrigger(true);
- // Run non-mandatory trigger.
- // These checks only work if the Executing SpellAbility is an Ability_Sub.
- if ((exSA instanceof AbilitySub) && !SpellApiToAi.Converter.get(exSA.getApi()).doTriggerAI(ai, exSA, false)) {
- // AI would not run this trigger if given the chance
- return sacrificed;
- }
- }
- }
- }
+ // Run non-mandatory trigger.
+ // These checks only work if the Executing SpellAbility is an Ability_Sub.
+ if ((exSA instanceof AbilitySub) && !SpellApiToAi.Converter.get(exSA.getApi()).doTriggerAI(ai, exSA, false)) {
+ // AI would not run this trigger if given the chance
+ return sacrificed;
+ }
+ }
+ }
+ }
remaining = CardLists.filter(remaining, new Predicate() {
@Override
public boolean apply(final Card c) {
@@ -834,7 +819,7 @@ public class ComputerUtil {
if (ai.isOpponentOf(c.getController()))
return c;
}
-
+
if (destroy) {
final CardCollection indestructibles = CardLists.getKeyword(remaining, Keyword.INDESTRUCTIBLE);
if (!indestructibles.isEmpty()) {
@@ -924,7 +909,7 @@ public class ComputerUtil {
} catch (final Exception ex) {
throw new RuntimeException(TextUtil.concatNoSpace("There is an error in the card code for ", c.getName(), ":", ex.getMessage()), ex);
- }
+ }
}
}
@@ -973,16 +958,16 @@ public class ComputerUtil {
final Card card = sa.getHostCard();
if (card.hasSVar("PlayMain1")) {
- if (card.getSVar("PlayMain1").equals("ALWAYS") || sa.getPayCosts().hasNoManaCost()) {
- return true;
- } else if (card.getSVar("PlayMain1").equals("OPPONENTCREATURES")) {
- //Only play these main1 when the opponent has creatures (stealing and giving them haste)
- if (!ai.getOpponents().getCreaturesInPlay().isEmpty()) {
- return true;
- }
- } else if (!card.getController().getCreaturesInPlay().isEmpty()) {
- return true;
- }
+ if (card.getSVar("PlayMain1").equals("ALWAYS") || sa.getPayCosts().hasNoManaCost()) {
+ return true;
+ } else if (card.getSVar("PlayMain1").equals("OPPONENTCREATURES")) {
+ //Only play these main1 when the opponent has creatures (stealing and giving them haste)
+ if (!ai.getOpponents().getCreaturesInPlay().isEmpty()) {
+ return true;
+ }
+ } else if (!card.getController().getCreaturesInPlay().isEmpty()) {
+ return true;
+ }
}
// try not to cast Raid creatures in main 1 if an attack is likely
@@ -995,7 +980,7 @@ public class ComputerUtil {
}
if (card.getManaCost().isZero()) {
- return true;
+ return true;
}
if (card.hasKeyword(Keyword.RIOT) && ChooseGenericEffectAi.preferHasteForRiot(sa, ai)) {
@@ -1023,9 +1008,9 @@ public class ComputerUtil {
&& (card.hasKeyword(Keyword.HASTE) || ComputerUtil.hasACardGivingHaste(ai, true) || sa.isDash())) {
return true;
}
-
+
if (card.hasKeyword(Keyword.EXALTED)) {
- return true;
+ return true;
}
//cast equipments in Main1 when there are creatures to equip and no other unequipped equipment
@@ -1155,7 +1140,7 @@ public class ComputerUtil {
if (discard.hasSVar("DiscardMe")) {
return true;
}
-
+
final Game game = ai.getGame();
final CardCollection landsInPlay = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.LANDS);
final CardCollection landsInHand = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS);
@@ -1255,11 +1240,11 @@ public class ComputerUtil {
}
}
} // AntiBuffedBy
-
- if (sub != null) {
+
+ if (sub != null) {
return castSpellInMain1(ai, sub);
}
-
+
return false;
}
@@ -1268,7 +1253,7 @@ public class ComputerUtil {
int activations = sa.getActivationsThisTurn();
if (!sa.isIntrinsic()) {
- return MyRandom.getRandom().nextFloat() >= .95; // Abilities created by static abilities have no memory
+ return MyRandom.getRandom().nextFloat() >= .95; // Abilities created by static abilities have no memory
}
if (activations < 10) { //10 activations per turn should still be acceptable
@@ -1285,27 +1270,27 @@ public class ComputerUtil {
return false;
}
if (abCost.hasTapCost() && source.hasSVar("AITapDown")) {
- return true;
+ return true;
} else if (sa.hasParam("Planeswalker") && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
- for (final CostPart part : abCost.getCostParts()) {
- if (part instanceof CostPutCounter) {
- return true;
- }
- }
+ for (final CostPart part : abCost.getCostParts()) {
+ if (part instanceof CostPutCounter) {
+ return true;
+ }
+ }
}
for (final CostPart part : abCost.getCostParts()) {
if (part instanceof CostSacrifice) {
final CostSacrifice sac = (CostSacrifice) part;
-
+
final String type = sac.getType();
-
+
if (type.equals("CARDNAME")) {
if (source.getSVar("SacMe").equals("6")) {
return true;
}
continue;
}
-
+
final CardCollection typeList =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(","), source.getController(), source, sa);
for (Card c : typeList) {
@@ -1340,14 +1325,14 @@ public class ComputerUtil {
Map params = stAb.getMapParams();
if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword")
&& params.get("AddKeyword").contains("Haste")) {
-
+
if (c.isEquipment() && c.getEquipping() == null) {
return true;
}
final String affected = params.get("Affected");
if (affected.contains("Creature.YouCtrl")
- || affected.contains("Other+YouCtrl")) {
+ || affected.contains("Other+YouCtrl")) {
return true;
} else if (affected.contains("Creature.PairedWith") && !c.isPaired()) {
return true;
@@ -1356,10 +1341,10 @@ public class ComputerUtil {
}
for (Trigger t : c.getTriggers()) {
- Map params = t.getMapParams();
+ Map params = t.getMapParams();
if (!"ChangesZone".equals(params.get("Mode"))
- || !"Battlefield".equals(params.get("Destination"))
- || !params.containsKey("ValidCard")) {
+ || !"Battlefield".equals(params.get("Destination"))
+ || !params.containsKey("ValidCard")) {
continue;
}
@@ -1367,7 +1352,7 @@ public class ComputerUtil {
if (valid.contains("Creature.YouCtrl")
|| valid.contains("Other+YouCtrl") ) {
- final SpellAbility sa = t.getTriggeredSA();
+ final SpellAbility sa = t.getOverridingAbility();
if (sa != null && sa.getApi() == ApiType.Pump && sa.hasParam("KW")
&& sa.getParam("KW").contains("Haste")) {
return true;
@@ -1375,10 +1360,10 @@ public class ComputerUtil {
}
}
}
-
+
all.addAll(ai.getCardsActivableInExternalZones(true));
all.addAll(ai.getCardsIn(ZoneType.Hand));
-
+
for (final Card c : all) {
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.getApi() == ApiType.Pump && sa.hasParam("KW") && sa.getParam("KW").contains("Haste")) {
@@ -1413,10 +1398,10 @@ public class ComputerUtil {
public static boolean hasAFogEffect(final Player ai) {
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
-
+
all.addAll(ai.getCardsActivableInExternalZones(true));
all.addAll(ai.getCardsIn(ZoneType.Hand));
-
+
for (final Card c : all) {
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.getApi() != ApiType.Fog) {
@@ -1446,7 +1431,7 @@ public class ComputerUtil {
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
all.addAll(ai.getCardsActivableInExternalZones(true));
all.addAll(CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.not(Presets.PERMANENTS)));
-
+
for (final Card c : all) {
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.getApi() != ApiType.DealDamage) {
@@ -1505,7 +1490,7 @@ public class ComputerUtil {
/**
* Returns list of objects threatened by effects on the stack
- *
+ *
* @param ai
* calling player
* @param sa
@@ -1520,7 +1505,7 @@ public class ComputerUtil {
if (game.getStack().isEmpty()) {
return objects;
}
-
+
// check stack for something that will kill this
for (SpellAbilityStackInstance si : game.getStack()) {
// iterate from top of stack to find SpellAbility, including sub-abilities,
@@ -1538,8 +1523,8 @@ public class ComputerUtil {
if (top) {
break; // only evaluate top-stack
}
- }
-
+ }
+
return objects;
}
@@ -1551,14 +1536,14 @@ public class ComputerUtil {
int toughness = 0;
boolean grantIndestructible = false;
boolean grantShroud = false;
-
+
if (topStack == null) {
return objects;
}
-
+
final Card source = topStack.getHostCard();
final ApiType threatApi = topStack.getApi();
-
+
// Can only Predict things from AFs
if (threatApi == null) {
return threatened;
@@ -1572,7 +1557,7 @@ public class ComputerUtil {
CardCollectionView battleField = aiPlayer.getCardsIn(ZoneType.Battlefield);
objects = CardLists.getValidCards(battleField, topStack.getParam("ValidCards").split(","), source.getController(), source, topStack);
} else {
- return threatened;
+ return threatened;
}
} else {
objects = topStack.getTargets().getTargets();
@@ -1586,7 +1571,7 @@ public class ComputerUtil {
}
}
if (canBeTargeted.isEmpty()) {
- return threatened;
+ return threatened;
}
objects = canBeTargeted;
}
@@ -1655,7 +1640,7 @@ public class ComputerUtil {
}
// don't use it on creatures that can't be regenerated
- if ((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) &&
+ if ((saviourApi == ApiType.Regenerate || saviourApi == ApiType.RegenerateAll) &&
(!c.canBeShielded() || noRegen)) {
continue;
}
@@ -1667,14 +1652,14 @@ public class ComputerUtil {
continue;
}
}
-
+
if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) {
boolean canSave = ComputerUtilCombat.predictDamageTo(c, dmg - toughness, source, false) < ComputerUtilCombat.getDamageToKill(c);
if (!canSave) {
continue;
}
}
-
+
// cannot protect against source
if (saviourApi == ApiType.Protection && (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
@@ -1685,7 +1670,7 @@ public class ComputerUtil {
if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) {
continue;
}
-
+
if (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c)) {
threatened.add(c);
}
@@ -1704,7 +1689,7 @@ public class ComputerUtil {
}
// -Toughness Curse
else if ((threatApi == ApiType.Pump || threatApi == ApiType.PumpAll && topStack.isCurse())
- && (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
+ && (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|| saviourApi == ApiType.Protection || saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll
|| saviourApi == null)) {
final int dmg = -AbilityUtils.calculateAmount(topStack.getHostCard(),
@@ -1717,7 +1702,7 @@ public class ComputerUtil {
if (!canRemove) {
continue;
}
-
+
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) {
final boolean cantSave = c.getNetToughness() + toughness <= dmg
|| (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && !grantIndestructible
@@ -1726,14 +1711,14 @@ public class ComputerUtil {
continue;
}
}
-
+
if (saviourApi == ApiType.PutCounter || saviourApi == ApiType.PutCounterAll) {
boolean canSave = c.getNetToughness() + toughness > dmg;
if (!canSave) {
continue;
}
}
-
+
if (saviourApi == ApiType.Protection) {
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
@@ -1827,9 +1812,9 @@ public class ComputerUtil {
}
}
//GainControl
- else if ((threatApi == ApiType.GainControl
- || (threatApi == ApiType.Attach && topStack.hasParam("AILogic") && topStack.getParam("AILogic").equals("GainControl") ))
- && (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
+ else if ((threatApi == ApiType.GainControl
+ || (threatApi == ApiType.Attach && topStack.hasParam("AILogic") && topStack.getParam("AILogic").equals("GainControl") ))
+ && (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|| saviourApi == ApiType.Protection || saviourApi == null)) {
for (final Object o : objects) {
if (o instanceof Card) {
@@ -1946,7 +1931,7 @@ public class ComputerUtil {
public static int scoreHand(CardCollectionView handList, Player ai, int cardsToReturn) {
// TODO Improve hand scoring in relation to cards to return.
// If final hand size is 5, score a hand based on what that 5 would be.
- // Or if this is really really fast, determine what the 5 would be based on scoring
+ // Or if this is really really fast, determine what the 5 would be based on scoring
// All of the possibilities
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
@@ -2029,16 +2014,16 @@ public class ComputerUtil {
final CardCollectionView handList = ai.getCardsIn(ZoneType.Hand);
return scoreHand(handList, ai, cardsToReturn) <= 0;
}
-
+
public static CardCollection getPartialParisCandidates(Player ai) {
// Commander no longer uses partial paris.
final CardCollection candidates = new CardCollection();
final CardCollectionView handList = ai.getCardsIn(ZoneType.Hand);
-
+
final CardCollection lands = CardLists.getValidCards(handList, "Card.Land", ai, null);
final CardCollection nonLands = CardLists.getValidCards(handList, "Card.nonLand", ai, null);
CardLists.sortByCmcDesc(nonLands);
-
+
if (lands.size() >= 3 && lands.size() <= 4) {
return candidates;
}
@@ -2046,7 +2031,7 @@ public class ComputerUtil {
//Not enough lands!
int tgtCandidates = Math.max(Math.abs(lands.size()-nonLands.size()), 3);
System.out.println("Partial Paris: " + ai.getName() + " lacks lands, aiming to exile " + tgtCandidates + " cards.");
-
+
for (int i=0;i manaArts = Arrays.asList("Mox Pearl", "Mox Sapphire", "Mox Jet", "Mox Ruby", "Mox Emerald");
-
+
// evaluate creatures available in deck
CardCollectionView allCreatures = CardLists.filter(allCards, Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isOwner(player)));
int numCards = allCreatures.size();
@@ -2200,7 +2185,7 @@ public class ComputerUtil {
}
Collections.sort(goodChoices, CardLists.TextLenComparator);
-
+
CardLists.sortByCmcDesc(goodChoices);
dChoices.add(goodChoices.get(0));
@@ -2211,15 +2196,18 @@ public class ComputerUtil {
if (p == aiChooser) { // ask that ai player what he would like to discard
final AiController aic = ((PlayerControllerAi)p.getController()).getAi();
return aic.getCardsToDiscard(min, max, validCards, sa);
- }
+ }
// no special options for human or remote friends
return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max);
}
- public static String chooseSomeType(Player ai, String kindOfType, String logic, List invalidTypes) {
+ public static String chooseSomeType(Player ai, String kindOfType, String logic, Collection validTypes, List invalidTypes) {
if (invalidTypes == null) {
invalidTypes = ImmutableList.of();
}
+ if (validTypes == null) {
+ validTypes = ImmutableList.of();
+ }
final Game game = ai.getGame();
String chosen = "";
@@ -2243,7 +2231,7 @@ public class ComputerUtil {
}
}
if (StringUtils.isEmpty(chosen)) {
- chosen = "Creature";
+ chosen = validTypes.isEmpty() ? "Creature" : Aggregates.random(validTypes);
}
} else if (kindOfType.equals("Creature")) {
if (logic != null) {
@@ -2257,7 +2245,7 @@ public class ComputerUtil {
chosen = ComputerUtilCard.getMostProminentType(ai.getCardsIn(ZoneType.Battlefield), valid);
}
else if (logic.equals("MostProminentOppControls")) {
- CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
+ CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
chosen = ComputerUtilCard.getMostProminentType(list, valid);
if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) {
list = CardLists.filterControlledBy(game.getCardsInGame(), ai.getOpponents());
@@ -2284,11 +2272,11 @@ public class ComputerUtil {
chosen = ComputerUtilCard.getMostProminentType(list, valid);
} else if (logic.equals("MostNeededType")) {
- // Choose a type that is in the deck, but not in hand or on the battlefield
+ // Choose a type that is in the deck, but not in hand or on the battlefield
final List basics = new ArrayList<>(CardType.Constant.BASIC_TYPES);
CardCollectionView presentCards = CardCollection.combine(ai.getCardsIn(ZoneType.Battlefield), ai.getCardsIn(ZoneType.Hand));
CardCollectionView possibleCards = ai.getAllCards();
-
+
for (String b : basics) {
if (!Iterables.any(presentCards, CardPredicates.isType(b)) && Iterables.any(possibleCards, CardPredicates.isType(b))) {
chosen = b;
@@ -2338,13 +2326,15 @@ public class ComputerUtil {
return chosen;
}
- public static Object vote(Player ai, List
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
- *
+ *
* @return a boolean.
*/
@Override
@@ -170,7 +170,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
* a {@link forge.game.spellability.SpellAbility} object.
* @param mandatory
* a boolean.
- *
+ *
* @return a boolean.
*/
@Override
@@ -370,10 +370,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (!activateForCost && list.isEmpty()) {
return false;
}
- if ("Atarka's Command".equals(sourceName)
- && (list.size() < 2 || ai.getLandsPlayedThisTurn() < 1)) {
- // be strict on playing lands off charms
- return false;
+ if ("Atarka's Command".equals(sourceName)
+ && (list.size() < 2 || ai.getLandsPlayedThisTurn() < 1)) {
+ // be strict on playing lands off charms
+ return false;
}
String num = sa.getParam("ChangeNum");
@@ -385,7 +385,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
source.setSVar("PayX", Integer.toString(xPay));
}
}
-
+
if (sourceName.equals("Temur Sabertooth")) {
// activated bounce + pump
if (ComputerUtilCard.shouldPumpCard(ai, sa.getSubAbility(), source, 0, 0, Arrays.asList("Indestructible")) ||
@@ -400,9 +400,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
-
+
if (ComputerUtil.playImmediately(ai, sa)) {
- return true;
+ return true;
}
// don't use fetching to top of library/graveyard before main2
@@ -418,9 +418,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
if (ComputerUtil.waitForBlocking(sa)) {
- return false;
+ return false;
}
-
+
final AbilitySub subAb = sa.getSubAbility();
return subAb == null || SpellApiToAi.Converter.get(subAb.getApi()).chkDrawbackWithSubs(ai, subAb);
}
@@ -551,7 +551,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
* basicManaFixing.
*
* @param ai
- *
+ *
* @param list
* a List object.
* @return a {@link forge.game.card.Card} object.
@@ -584,7 +584,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (minType != null) {
result = CardLists.getType(list, minType);
}
-
+
// pick dual lands if available
if (Iterables.any(result, Predicates.not(CardPredicates.Presets.BASIC_LANDS))) {
result = CardLists.filter(result, Predicates.not(CardPredicates.Presets.BASIC_LANDS));
@@ -597,7 +597,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
*
* areAllBasics.
*
- *
+ *
* @param types
* a {@link java.lang.String} object.
* @return a boolean.
@@ -617,8 +617,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
* @return Card
*/
private static Card chooseCreature(final Player ai, CardCollection list) {
- // Creating a new combat for testing purposes.
- final Player opponent = ai.getWeakestOpponent();
+ // Creating a new combat for testing purposes.
+ final Player opponent = ai.getWeakestOpponent();
Combat combat = new Combat(opponent);
for (Card att : opponent.getCreaturesInPlay()) {
combat.addAttacker(att, ai);
@@ -742,7 +742,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
@@ -781,7 +781,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false;
}
}
-
+
//don't unearth after attacking is possible
if (sa.hasParam("Unearth") && ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
return false;
@@ -895,7 +895,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
return false;
}
-
+
immediately |= ComputerUtil.playImmediately(ai, sa);
// Narrow down the list:
@@ -926,7 +926,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
CardCollection blockers = currCombat.getBlockers(attacker);
// Save my attacker by bouncing a blocker
if (attacker.getController().equals(ai) && attacker.getShieldCount() == 0
- && ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, currCombat)
+ && ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, currCombat)
&& !currCombat.getBlockers(attacker).isEmpty()) {
ComputerUtilCard.sortByEvaluateCreature(blockers);
Combat combat = new Combat(ai);
@@ -970,9 +970,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
sa.getTargets().add(tobounce);
- boolean saheeliFelidarCombo = sa.getHostCard().getName().equals("Felidar Guardian")
+ boolean saheeliFelidarCombo = sa.getHostCard().getName().equals("Felidar Guardian")
&& tobounce.getName().equals("Saheeli Rai")
- && CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Felidar Guardian")).size() <
+ && CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Felidar Guardian")).size() <
CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Creature")).size() + ai.getOpponentsGreatestLifeTotal() + 10;
// remember that the card was bounced already unless it's a special combo case
@@ -985,20 +985,20 @@ public class ChangeZoneAi extends SpellAbilityAi {
// bounce opponent's stuff
list = CardLists.filterControlledBy(list, ai.getOpponents());
if (!CardLists.getNotType(list, "Land").isEmpty()) {
- // When bouncing opponents stuff other than lands, don't bounce cards with CMC 0
- list = CardLists.filter(list, new Predicate() {
- @Override
- public boolean apply(final Card c) {
- for (Card aura : c.getEnchantedBy()) {
+ // When bouncing opponents stuff other than lands, don't bounce cards with CMC 0
+ list = CardLists.filter(list, new Predicate() {
+ @Override
+ public boolean apply(final Card c) {
+ for (Card aura : c.getEnchantedBy()) {
return aura.getController().isOpponentOf(ai);
- }
- if (blink) {
- return c.isToken();
- } else {
- return c.isToken() || c.getCMC() > 0;
- }
- }
- });
+ }
+ if (blink) {
+ return c.isToken();
+ } else {
+ return c.isToken() || c.getCMC() > 0;
+ }
+ }
+ });
}
// TODO: Blink permanents with ETB triggers
/*else if (!sa.isTrigger() && SpellAbilityAi.playReusable(ai, sa)) {
@@ -1023,7 +1023,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
} else if (origin.contains(ZoneType.Graveyard)) {
- if (destination.equals(ZoneType.Exile) || destination.equals(ZoneType.Library)) {
+ if (destination.equals(ZoneType.Exile) || destination.equals(ZoneType.Library)) {
// Don't use these abilities before main 2 if possible
if (!immediately && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) {
@@ -1035,7 +1035,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
&& !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
- } else if (destination.equals(ZoneType.Hand)) {
+ } else if (destination.equals(ZoneType.Hand)) {
// only retrieve cards from computer graveyard
list = CardLists.filterControlledBy(list, ai);
} else if (sa.hasParam("AttachedTo")) {
@@ -1065,8 +1065,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (destination.equals(ZoneType.Exile) || origin.contains(ZoneType.Battlefield)) {
// don't rush bouncing stuff when not going to attack
- if (!immediately && sa.getPayCosts() != null
- && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
+ if (!immediately && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& game.getPhaseHandler().isPlayerTurn(ai)
&& ai.getCreaturesInPlay().isEmpty()) {
return false;
@@ -1097,14 +1096,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
// Only care about combatants during combat
if (game.getPhaseHandler().inCombat() && origin.contains(ZoneType.Battlefield)) {
- CardCollection newList = CardLists.getValidCards(list, "Card.attacking,Card.blocking", null, null);
- if (!newList.isEmpty() || !sa.isTrigger()) {
- list = newList;
- }
+ CardCollection newList = CardLists.getValidCards(list, "Card.attacking,Card.blocking", null, null);
+ if (!newList.isEmpty() || !sa.isTrigger()) {
+ list = newList;
+ }
}
- boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.getTargetRestrictions() != null
- && sa.getTargetRestrictions().getMinTargets(source, sa) == 0 && sa.getPayCosts() != null
+ boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.usesTargeting()
+ && sa.getTargetRestrictions().getMinTargets(source, sa) == 0
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
if (list.isEmpty() && !doWithoutTarget) {
@@ -1252,7 +1251,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
return true;
}
-
+
/**
* Checks if a permanent threatened by a stack ability or in combat can
* be saved by bouncing.
@@ -1325,11 +1324,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
Collections.sort(aiPlaneswalkers, new Comparator() {
@Override
public int compare(final Card a, final Card b) {
- return a.getCounters(CounterType.LOYALTY) - b.getCounters(CounterType.LOYALTY);
+ return a.getCounters(CounterEnumType.LOYALTY) - b.getCounters(CounterEnumType.LOYALTY);
}
});
for (Card pw : aiPlaneswalkers) {
- int curLoyalty = pw.getCounters(CounterType.LOYALTY);
+ int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
int freshLoyalty = Integer.valueOf(pw.getCurrentState().getBaseLoyalty());
if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
return pw;
@@ -1507,10 +1506,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (type == null) {
type = "Card";
}
-
+
Card c = null;
final Player activator = sa.getActivatingPlayer();
-
+
CardLists.shuffle(fetchList);
// Save a card as a default, in case we can't find anything suitable.
Card first = fetchList.get(0);
@@ -1615,19 +1614,19 @@ public class ChangeZoneAi extends SpellAbilityAi {
// AI was never asked
return true;
}
-
+
@Override
- public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) {
+ public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
// Called when looking for creature to attach aura or equipment
return ComputerUtilCard.getBestAI(options);
}
-
+
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
*/
@Override
- public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options) {
+ public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options, Map params) {
// Currently only used by Curse of Misfortunes, so this branch should never get hit
// But just in case it does, just select the first option
return Iterables.getFirst(options, null);
@@ -1790,6 +1789,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
public boolean doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
+ @SuppressWarnings("unchecked")
Map originalParams = (Map)sa.getReplacingObject(AbilityKey.OriginalParams);
SpellAbility causeSa = (SpellAbility)originalParams.get(AbilityKey.Cause);
SpellAbility causeSub = null;
@@ -1801,7 +1801,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (causeSa != null && (causeSub = causeSa.getSubAbility()) != null) {
ApiType subApi = causeSub.getApi();
-
+
if (subApi == ApiType.ChangeZone && "Exile".equals(causeSub.getParam("Origin"))
&& "Battlefield".equals(causeSub.getParam("Destination"))) {
// A blink effect implemented using ChangeZone API
@@ -1817,7 +1817,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
} else return causeSa.getHostCard() == null || !causeSa.getHostCard().equals(sa.getReplacingObject(AbilityKey.Card))
|| !causeSa.getActivatingPlayer().equals(aiPlayer);
}
-
+
// Normally we want the commander back in Command zone to recast him later
return true;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java
index 1f9343599df..296a695b871 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java
@@ -11,6 +11,7 @@ import forge.util.MyRandom;
import forge.util.collect.FCollection;
import java.util.List;
+import java.util.Map;
public class CharmAi extends SpellAbilityAi {
@Override
@@ -232,7 +233,7 @@ public class CharmAi extends SpellAbilityAi {
}
@Override
- public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable opponents) {
+ public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable opponents, Map params) {
return Aggregates.random(opponents);
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
index 1beefd77b09..f8a48d2ec25 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
@@ -20,7 +21,7 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
-import forge.game.card.CounterType;
+import forge.game.card.CounterEnumType;
import forge.game.combat.Combat;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
@@ -99,7 +100,7 @@ public class ChooseCardAi extends SpellAbilityAi {
});
return !choices.isEmpty();
} else if (aiLogic.equals("Ashiok")) {
- final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
+ final int loyalty = host.getCounters(CounterEnumType.LOYALTY) - 1;
for (int i = loyalty; i >= 0; i--) {
host.setSVar("ChosenX", "Number$" + i);
choices = ai.getGame().getCardsIn(choiceZone);
@@ -140,12 +141,12 @@ public class ChooseCardAi extends SpellAbilityAi {
}
return checkApiLogic(ai, sa);
}
-
+
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
- public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) {
+ public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
final Card host = sa.getHostCard();
final Player ctrl = host.getController();
final String logic = sa.getParam("AILogic");
@@ -191,7 +192,7 @@ public class ChooseCardAi extends SpellAbilityAi {
if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) {
return false;
}
- int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
+ int ref = ComputerUtilAbility.getAbilitySourceName(sa).equals("Forcefield") ? 1 : 0;
return ComputerUtilCombat.damageIfUnblocked(c, ai, combat, true) > ref;
}
});
@@ -233,7 +234,7 @@ public class ChooseCardAi extends SpellAbilityAi {
return false;
}
for (SpellAbility sa : c.getAllSpellAbilities()) {
- if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
+ if (sa.getPayCosts().hasTapCost()) {
return false;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java
index ff0bfc2ceaa..f9c3b3d9798 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java
@@ -1,6 +1,7 @@
package forge.ai.ability;
import java.util.List;
+import java.util.Map;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@@ -23,7 +24,6 @@ public class ChooseCardNameAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
- Card source = sa.getHostCard();
if (sa.hasParam("AILogic")) {
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
@@ -60,7 +60,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
- public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) {
+ public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
return ComputerUtilCard.getBestAI(options);
}
@@ -86,7 +86,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
if (rules.getSplitType() == CardSplitType.Split) {
Card copy = CardUtil.getLKICopy(card);
- // for calcing i need only one split side
+ // for calcing i need only one split side
if (isOther) {
copy.getCurrentState().copyFrom(card.getState(CardStateName.RightSplit), true);
} else {
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCompanionAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCompanionAi.java
new file mode 100644
index 00000000000..1240d9ab54f
--- /dev/null
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCompanionAi.java
@@ -0,0 +1,29 @@
+package forge.ai.ability;
+
+import com.google.common.collect.Lists;
+import forge.ai.SpellAbilityAi;
+import forge.game.card.Card;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class ChooseCompanionAi extends SpellAbilityAi {
+
+ /* (non-Javadoc)
+ * @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
+ */
+ @Override
+ public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
+ List cards = Lists.newArrayList(options);
+ if (cards.isEmpty()) {
+ return null;
+ }
+
+ Collections.shuffle(cards);
+ return cards.get(0);
+ }
+}
+
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseDirectionAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseDirectionAi.java
index b3ae74d5274..c2cccc1f1f0 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseDirectionAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseDirectionAi.java
@@ -46,6 +46,6 @@ public class ChooseDirectionAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
- return canPlayAI(ai, sa);
+ return mandatory || canPlayAI(ai, sa);
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
new file mode 100644
index 00000000000..e5ac8a99d6d
--- /dev/null
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
@@ -0,0 +1,36 @@
+package forge.ai.ability;
+
+import forge.ai.SpellAbilityAi;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+import forge.game.spellability.TargetRestrictions;
+import forge.util.MyRandom;
+
+public class ChooseEvenOddAi extends SpellAbilityAi {
+
+ @Override
+ protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
+ if (!sa.hasParam("AILogic")) {
+ return false;
+ }
+ TargetRestrictions tgt = sa.getTargetRestrictions();
+ if (tgt != null) {
+ sa.resetTargets();
+ Player opp = aiPlayer.getWeakestOpponent();
+ if (sa.canTarget(opp)) {
+ sa.getTargets().add(opp);
+ } else {
+ return false;
+ }
+ }
+ boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
+ return chance;
+ }
+
+ @Override
+ protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
+ return mandatory || canPlayAI(ai, sa);
+ }
+
+}
+
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
index 1f4e627bda8..b814b347224 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
@@ -180,25 +180,25 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
Card imprinted = host.getImprintedCards().getFirst();
int dmg = imprinted.getCMC();
Player owner = imprinted.getOwner();
-
+
//useless cards in hand
if (imprinted.getName().equals("Bridge from Below") ||
imprinted.getName().equals("Haakon, Stromgald Scourge")) {
return allow;
}
-
+
//bad cards when are thrown from the library to the graveyard, but Yixlid can prevent that
if (!player.getGame().isCardInPlay("Yixlid Jailer") && (
imprinted.getName().equals("Gaea's Blessing") ||
imprinted.getName().equals("Narcomoeba"))) {
return allow;
}
-
+
// milling against Tamiyo is pointless
if (owner.isCardInCommand("Emblem - Tamiyo, the Moon Sage")) {
return allow;
}
-
+
// milling a land against Gitrog result in card draw
if (imprinted.isLand() && owner.isCardInPlay("The Gitrog Monster")) {
// try to mill owner
@@ -207,19 +207,19 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
}
return allow;
}
-
+
// milling a creature against Sidisi result in more creatures
if (imprinted.isCreature() && owner.isCardInPlay("Sidisi, Brood Tyrant")) {
return allow;
}
- //if Iona does prevent from casting, allow it to draw
+ //if Iona does prevent from casting, allow it to draw
for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) {
if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) {
return allow;
}
}
-
+
if (dmg == 0) {
// If CMC = 0, mill it!
return deny;
@@ -244,7 +244,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
// check for something which might prevent the counters to be placed on host
- if (!host.canReceiveCounters(CounterType.P1P1)) {
+ if (!host.canReceiveCounters(CounterEnumType.P1P1)) {
return tokenSA;
}
@@ -256,7 +256,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
// need a copy for one with extra +1/+1 counter boost,
// without causing triggers to run
final Card copy = CardUtil.getLKICopy(host);
- copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
+ copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n);
copy.setZone(host.getZone());
// if host would put into the battlefield attacking
@@ -281,10 +281,10 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
// TODO check for trigger to turn token ETB into +1/+1 counter for host
// TODO check for trigger to turn token ETB into damage or life loss for opponent
// in this cases Token might be prefered even if they would not survive
- final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
+ final Card tokenCard = TokenAi.spawnToken(player, tokenSA);
- // Token would not survive
- if (tokenCard.getNetToughness() < 1) {
+ // Token would not survive
+ if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) {
return counterSA;
}
@@ -336,7 +336,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
filtered.add(sp);
}
}
-
+
// TODO find better way to check
if (!filtered.isEmpty()) {
return filtered.get(0);
@@ -362,7 +362,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
game.getAction().checkStaticAbilities(false);
// can't gain counters, use Haste
- if (!copy.canReceiveCounters(CounterType.P1P1)) {
+ if (!copy.canReceiveCounters(CounterEnumType.P1P1)) {
return true;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChoosePlayerAi.java b/forge-ai/src/main/java/forge/ai/ability/ChoosePlayerAi.java
index ddb90e701f1..5b98b108cda 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChoosePlayerAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChoosePlayerAi.java
@@ -9,6 +9,7 @@ import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.List;
+import java.util.Map;
public class ChoosePlayerAi extends SpellAbilityAi {
@Override
@@ -27,7 +28,7 @@ public class ChoosePlayerAi extends SpellAbilityAi {
}
@Override
- public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable choices) {
+ public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable choices, Map params) {
Player chosen = null;
if ("Curse".equals(sa.getParam("AILogic"))) {
for (Player pc : choices) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
index f7a694e0c67..038f9a843ab 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
@@ -1,6 +1,7 @@
package forge.ai.ability;
import java.util.List;
+import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
@@ -126,7 +127,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
@Override
- public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) {
+ public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
diff --git a/forge-ai/src/main/java/forge/ai/ability/ClashAi.java b/forge-ai/src/main/java/forge/ai/ability/ClashAi.java
index 5800ecefb32..b9f7e8d7e6f 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ClashAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ClashAi.java
@@ -1,6 +1,8 @@
package forge.ai.ability;
+import java.util.Map;
+
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtilCard;
@@ -56,7 +58,7 @@ public class ClashAi extends SpellAbilityAi {
* forge.game.spellability.SpellAbility, java.lang.Iterable)
*/
@Override
- protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options) {
+ protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options, Map params) {
for (Player p : options) {
if (p.getCardsIn(ZoneType.Library).size() == 0)
return p;
@@ -82,7 +84,7 @@ public class ClashAi extends SpellAbilityAi {
PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
// use chooseSinglePlayer function to the select player
- Player chosen = chooseSinglePlayer(ai, sa, players);
+ Player chosen = chooseSinglePlayer(ai, sa, players, null);
if (chosen != null) {
sa.resetTargets();
sa.getTargets().add(chosen);
diff --git a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java
index 3d6870d033d..3a3de604029 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java
@@ -15,6 +15,7 @@ import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.List;
+import java.util.Map;
public class CloneAi extends SpellAbilityAi {
@@ -169,7 +170,7 @@ public class CloneAi extends SpellAbilityAi {
*/
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional,
- Player targetedPlayer) {
+ Player targetedPlayer, Map params) {
final Card host = sa.getHostCard();
final Player ctrl = host.getController();
diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
index 7a29ee98068..617da8098eb 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
@@ -18,6 +18,7 @@
package forge.ai.ability;
import java.util.List;
+import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
@@ -302,7 +303,7 @@ public class ControlGainAi extends SpellAbilityAi {
} // pumpDrawbackAI()
@Override
- protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options) {
+ protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options, Map params) {
final List cards = Lists.newArrayList();
for (Player p : options) {
cards.addAll(p.getCreaturesInPlay());
diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlGainVariantAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlGainVariantAi.java
new file mode 100644
index 00000000000..894a6951c15
--- /dev/null
+++ b/forge-ai/src/main/java/forge/ai/ability/ControlGainVariantAi.java
@@ -0,0 +1,82 @@
+/*
+ * Forge: Play Magic: the Gathering.
+ * Copyright (C) 2011 Forge Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package forge.ai.ability;
+
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+import forge.ai.ComputerUtilCard;
+import forge.ai.SpellAbilityAi;
+import forge.game.card.Card;
+import forge.game.card.CardLists;
+import forge.game.card.CardPredicates;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+import forge.game.zone.ZoneType;
+
+
+/**
+ *
+ * AbilityFactory_GainControlVariant class.
+ *
+ *
+ * @author Forge
+ * @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
+ */
+public class ControlGainVariantAi extends SpellAbilityAi {
+ @Override
+ protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
+
+ String logic = sa.getParam("AILogic");
+
+ if ("GainControlOwns".equals(logic)) {
+ List list = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), new Predicate() {
+ @Override
+ public boolean apply(final Card crd) {
+ return crd.isCreature() && !crd.getController().equals(crd.getOwner());
+ }
+ });
+ if (list.isEmpty()) {
+ return false;
+ }
+ for (final Card c : list) {
+ if (ai.equals(c.getController())) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+
+ }
+
+ @Override
+ public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
+ Iterable otherCtrl = CardLists.filter(options, Predicates.not(CardPredicates.isController(ai)));
+ if (Iterables.isEmpty(otherCtrl)) {
+ return ComputerUtilCard.getWorstAI(options);
+ } else {
+ return ComputerUtilCard.getBestAI(otherCtrl);
+ }
+ }
+
+}
diff --git a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java
index 7ce9dfbc473..e455230988f 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java
@@ -19,6 +19,7 @@ import forge.game.zone.ZoneType;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
public class CopyPermanentAi extends SpellAbilityAi {
@Override
@@ -204,7 +205,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
*/
@Override
- public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) {
+ public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
// Select a card to attach to
CardCollection betterOptions = getBetterOptions(ai, sa, options, isOptional);
if (!betterOptions.isEmpty()) {
@@ -223,7 +224,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
}
@Override
- protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options) {
+ protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options, Map params) {
final List cards = new PlayerCollection(options).getCreaturesInPlay();
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
diff --git a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java
index d130c18a666..25a05760964 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java
@@ -29,8 +29,8 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
final SpellAbility top = game.getStack().peekAbility();
if (top != null
- && top.getPayCosts() != null && top.getPayCosts().getCostMana() != null
- && sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null
+ && top.getPayCosts().getCostMana() != null
+ && sa.getPayCosts().getCostMana() != null
&& top.getPayCosts().getCostMana().getMana().getCMC() >= sa.getPayCosts().getCostMana().getMana().getCMC() + diff) {
// The copied spell has a significantly higher CMC than the copy spell, consider copying
chance = 100;
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersAi.java
index 2535c29505d..659b05de3cd 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersAi.java
@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
@@ -26,7 +26,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
-import forge.game.card.CounterType;
+import forge.game.card.CounterEnumType;
import forge.game.keyword.Keyword;
import forge.util.Aggregates;
@@ -35,7 +35,7 @@ import forge.util.Aggregates;
*
- *
+ *
* @param list
* a {@link forge.CardList} object.
* @param type
@@ -77,7 +77,7 @@ public abstract class CountersAi {
*
* chooseBoonTarget.
*
- *
+ *
* @param list
* a {@link forge.CardList} object.
* @param type
@@ -97,7 +97,7 @@ public abstract class CountersAi {
final CardCollection boon = CardLists.filter(list, new Predicate() {
@Override
public boolean apply(final Card c) {
- return c.getCounters(CounterType.DIVINITY) == 0;
+ return c.getCounters(CounterEnumType.DIVINITY) == 0;
}
});
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false);
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java
index bf142184a33..b6e75e62b91 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java
@@ -42,14 +42,14 @@ public class CountersMoveAi extends SpellAbilityAi {
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final Card host = sa.getHostCard();
final String type = sa.getParam("CounterType");
- final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
+ final CounterType cType = "Any".equals(type) ? null : CounterType.getType(type);
// Don't tap creatures that may be able to block
if (ComputerUtil.waitForBlocking(sa)) {
return false;
}
- if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
+ if (CounterEnumType.P1P1.equals(cType) && sa.hasParam("Source")) {
int amount = calcAmount(sa, cType);
final List srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
if (ph.getPlayerTurn().isOpponentOf(ai)) {
@@ -92,7 +92,7 @@ public class CountersMoveAi extends SpellAbilityAi {
// for Simic Fluxmage and other
return ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN);
- } else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
+ } else if (CounterEnumType.P1P1.equals(cType) && sa.hasParam("Defined")) {
// something like Cyptoplast Root-kin
if (ph.getPlayerTurn().isOpponentOf(ai)) {
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
@@ -115,6 +115,7 @@ public class CountersMoveAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
+ sa.resetTargets();
if (!moveTgtAI(ai, sa) && !mandatory) {
return false;
@@ -142,7 +143,7 @@ public class CountersMoveAi extends SpellAbilityAi {
final Card host = sa.getHostCard();
final String type = sa.getParam("CounterType");
- final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
+ final CounterType cType = "Any".equals(type) ? null : CounterType.getType(type);
final List srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
final List destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
@@ -189,7 +190,7 @@ public class CountersMoveAi extends SpellAbilityAi {
// check for some specific AI preferences
if ("DontMoveCounterIfLethal".equals(sa.getParam("AILogic"))) {
- return cType != CounterType.P1P1 || src.getNetToughness() - src.getTempToughnessBoost() - 1 > 0;
+ return !cType.is(CounterEnumType.P1P1) || src.getNetToughness() - src.getTempToughnessBoost() - 1 > 0;
}
}
// no target
@@ -234,7 +235,7 @@ public class CountersMoveAi extends SpellAbilityAi {
final Card host = sa.getHostCard();
final Game game = ai.getGame();
final String type = sa.getParam("CounterType");
- final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
+ final CounterType cType = "Any".equals(type) || "All".equals(type) ? null : CounterType.getType(type);
List tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
@@ -278,7 +279,7 @@ public class CountersMoveAi extends SpellAbilityAi {
// do not steal a P1P1 from Undying if it would die
// this way
- if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
+ if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
}
return true;
@@ -321,13 +322,13 @@ public class CountersMoveAi extends SpellAbilityAi {
}
// try to remove P1P1 from undying or evolve
- if (CounterType.P1P1.equals(cType)) {
+ if (CounterEnumType.P1P1.equals(cType)) {
if (card.hasKeyword(Keyword.UNDYING) || card.hasKeyword(Keyword.EVOLVE)
|| card.hasKeyword(Keyword.ADAPT)) {
return true;
}
}
- if (CounterType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
+ if (CounterEnumType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
return true;
}
@@ -382,10 +383,10 @@ public class CountersMoveAi extends SpellAbilityAi {
}
if (cType != null) {
- if (CounterType.P1P1.equals(cType) && card.hasKeyword(Keyword.UNDYING)) {
+ if (CounterEnumType.P1P1.equals(cType) && card.hasKeyword(Keyword.UNDYING)) {
return false;
}
- if (CounterType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
+ if (CounterEnumType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
return false;
}
@@ -393,7 +394,7 @@ public class CountersMoveAi extends SpellAbilityAi {
return false;
}
}
- return false;
+ return true;
}
});
@@ -452,7 +453,7 @@ public class CountersMoveAi extends SpellAbilityAi {
// or for source -> multiple defined
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional,
- Player targetedPlayer) {
+ Player targetedPlayer, Map params) {
if (sa.hasParam("AiLogic")) {
String logic = sa.getParam("AiLogic");
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java
index b20e99ace3b..1dc29e1a89b 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersMultiplyAi.java
@@ -16,6 +16,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
+import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
@@ -77,7 +78,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
final CounterType counterType = getCounterType(sa);
- if (!CounterType.P1P1.equals(counterType) && counterType != null) {
+ if (!CounterEnumType.P1P1.equals(counterType) && counterType != null) {
if (!sa.hasParam("ActivationPhases")) {
// Don't use non P1P1/M1M1 counters before main 2 if possible
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
@@ -147,15 +148,15 @@ public class CountersMultiplyAi extends SpellAbilityAi {
if (!aiList.isEmpty()) {
// counter type list to check
// first loyalty, then P1P!, then Charge Counter
- List typeList = Lists.newArrayList(CounterType.LOYALTY, CounterType.P1P1, CounterType.CHARGE);
- for (CounterType type : typeList) {
+ List typeList = Lists.newArrayList(CounterEnumType.LOYALTY, CounterEnumType.P1P1, CounterEnumType.CHARGE);
+ for (CounterEnumType type : typeList) {
// enough targets
if (!sa.canAddMoreTarget()) {
break;
}
- if (counterType == null || counterType == type) {
- addTargetsByCounterType(ai, sa, aiList, type);
+ if (counterType == null || counterType.is(type)) {
+ addTargetsByCounterType(ai, sa, aiList, CounterType.get(type));
}
}
}
@@ -164,7 +165,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
if (!oppList.isEmpty()) {
// not enough targets
if (sa.canAddMoreTarget()) {
- final CounterType type = CounterType.M1M1;
+ final CounterType type = CounterType.get(CounterEnumType.M1M1);
if (counterType == null || counterType == type) {
addTargetsByCounterType(ai, sa, oppList, type);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java
index 52538a0827c..7dd9618dafe 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java
@@ -15,6 +15,7 @@ import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
+import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -32,7 +33,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
for (final Player p : allies) {
// player has experience or energy counter
- if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) {
+ if (p.getCounters(CounterEnumType.EXPERIENCE) + p.getCounters(CounterEnumType.ENERGY) >= 1) {
allyExpOrEnergy = true;
}
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate() {
@@ -115,17 +116,19 @@ public class CountersProliferateAi extends SpellAbilityAi {
*/
@SuppressWarnings("unchecked")
@Override
- public T chooseSingleEntity(Player ai, SpellAbility sa, Collection options, boolean isOptional, Player targetedPlayer) {
+ public T chooseSingleEntity(Player ai, SpellAbility sa, Collection options, boolean isOptional, Player targetedPlayer, Map params) {
// Proliferate is always optional for all, no need to select best
+ final CounterType poison = CounterType.get(CounterEnumType.POISON);
+
// because countertype can't be chosen anymore, only look for posion counters
for (final Player p : Iterables.filter(options, Player.class)) {
if (p.isOpponentOf(ai)) {
- if (p.getCounters(CounterType.POISON) > 0 && p.canReceiveCounters(CounterType.POISON)) {
+ if (p.getCounters(poison) > 0 && p.canReceiveCounters(poison)) {
return (T)p;
}
} else {
- if (p.getCounters(CounterType.POISON) <= 5 || p.canReceiveCounters(CounterType.POISON)) {
+ if (p.getCounters(poison) <= 5 || p.canReceiveCounters(poison)) {
return (T)p;
}
}
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 d139920d0a4..c3448d4af15 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
@@ -35,7 +35,7 @@ public class CountersPutAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see forge.ai.SpellAbilityAi#willPayCosts(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.cost.Cost,
* forge.game.card.Card)
@@ -56,17 +56,17 @@ public class CountersPutAi extends SpellAbilityAi {
if (part instanceof CostRemoveCounter) {
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
final CounterType counterType = remCounter.counter;
- if (counterType.name().equals(type) && !aiLogic.startsWith("MoveCounter")) {
+ if (counterType.getName().equals(type) && !aiLogic.startsWith("MoveCounter")) {
return false;
}
if (!part.payCostFromSource()) {
- if (counterType.equals(CounterType.P1P1)) {
+ if (counterType.is(CounterEnumType.P1P1)) {
return false;
}
continue;
}
// don't kill the creature
- if (counterType.equals(CounterType.P1P1) && source.getLethalDamage() <= 1) {
+ if (counterType.is(CounterEnumType.P1P1) && source.getLethalDamage() <= 1) {
return false;
}
}
@@ -77,7 +77,7 @@ public class CountersPutAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
@@ -109,7 +109,7 @@ public class CountersPutAi extends SpellAbilityAi {
}
}
int maxLevel = Integer.parseInt(sa.getParam("MaxLevel"));
- return source.getCounters(CounterType.LEVEL) < maxLevel;
+ return source.getCounters(CounterEnumType.LEVEL) < maxLevel;
}
return super.checkPhaseRestrictions(ai, sa, ph);
@@ -146,7 +146,7 @@ public class CountersPutAi extends SpellAbilityAi {
if (abTgt.canTgtPlayer()) {
// try to kill opponent with Poison
PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
- PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterType.POISON, 9));
+ PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterEnumType.POISON, 9));
if (!poisonList.isEmpty()) {
sa.getTargets().add(poisonList.max(PlayerPredicates.compareByLife()));
return true;
@@ -157,13 +157,13 @@ public class CountersPutAi extends SpellAbilityAi {
// try to kill creature with -1/-1 counters if it can
// receive counters, execpt it has undying
CardCollection oppCreat = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
- CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterType.M1M1));
+ CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
oppCreatM1 = CardLists.filter(oppCreatM1, new Predicate() {
@Override
public boolean apply(Card input) {
- return input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.M1M1);
+ return input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.get(CounterEnumType.M1M1));
}
});
@@ -220,6 +220,8 @@ public class CountersPutAi extends SpellAbilityAi {
if ("Never".equals(logic)) {
return false;
+ } else if ("AlwaysWithNoTgt".equals(logic)) {
+ return true;
} else if ("AristocratCounters".equals(logic)) {
return PumpAi.doAristocratWithCountersLogic(sa, ai);
} else if ("PayEnergy".equals(logic)) {
@@ -242,7 +244,7 @@ public class CountersPutAi extends SpellAbilityAi {
int totBlkPower = Aggregates.sum(blocked, CardPredicates.Accessors.fnGetNetPower);
int totBlkToughness = Aggregates.min(blocked, CardPredicates.Accessors.fnGetNetToughness);
- int numActivations = ai.getCounters(CounterType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
+ int numActivations = ai.getCounters(CounterEnumType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
if (sa.getHostCard().getNetToughness() + numActivations > totBlkPower
|| sa.getHostCard().getNetPower() + numActivations >= totBlkToughness) {
return true;
@@ -257,7 +259,7 @@ public class CountersPutAi extends SpellAbilityAi {
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
return true;
}
- } else if (ai.getCounters(CounterType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
+ } else if (ai.getCounters(CounterEnumType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
// outside of combat, this logic only works if the relevant AI profile option is enabled
// and if there is enough energy saved
if (!onlyInCombat) {
@@ -291,7 +293,7 @@ public class CountersPutAi extends SpellAbilityAi {
if (sa.getConditions() != null && !sa.getConditions().areMet(sa) && sa.getSubAbility() == null) {
return false;
}
-
+
if (sourceName.equals("Feat of Resistance")) { // sub-ability should take precedence
CardCollection prot = ProtectAi.getProtectCreatures(ai, sa.getSubAbility());
if (!prot.isEmpty()) {
@@ -320,7 +322,7 @@ public class CountersPutAi extends SpellAbilityAi {
Game game = ai.getGame();
Combat combat = game.getCombat();
- if (!source.canReceiveCounters(CounterType.P1P1) || source.getCounters(CounterType.P1P1) > 0) {
+ if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
return false;
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
return doCombatAdaptLogic(source, amount, combat);
@@ -334,7 +336,7 @@ public class CountersPutAi extends SpellAbilityAi {
}
return FightAi.canFightAi(ai, sa, nPump, nPump);
}
-
+
if (amountStr.equals("X")) {
if (source.getSVar(amountStr).equals("Count$xPaid")) {
// By default, set PayX here to maximum value (used for most SAs of this type).
@@ -343,7 +345,7 @@ public class CountersPutAi extends SpellAbilityAi {
if (isClockwork) {
// Clockwork Avian and other similar cards: do not tap all mana for X,
// instead only rewind to max allowed value when the power gets low enough.
- int curCtrs = source.getCounters(CounterType.P1P0);
+ int curCtrs = source.getCounters(CounterEnumType.P1P0);
int maxCtrs = Integer.parseInt(sa.getParam("MaxFromEffect"));
// This will "rewind" clockwork cards when they fall to 50% power or below, consider improving
@@ -394,7 +396,7 @@ public class CountersPutAi extends SpellAbilityAi {
return true;
}
}
-
+
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
@@ -415,8 +417,8 @@ public class CountersPutAi extends SpellAbilityAi {
// Targeting
if (sa.usesTargeting()) {
- sa.resetTargets();
-
+ sa.resetTargets();
+
final boolean sacSelf = ComputerUtilCost.isSacrificeSelfCost(abCost);
if (sa.isCurse()) {
@@ -433,7 +435,7 @@ public class CountersPutAi extends SpellAbilityAi {
if (sacSelf && c.equals(source)) {
return false;
}
- return sa.canTarget(c) && c.canReceiveCounters(CounterType.valueOf(type));
+ return sa.canTarget(c) && c.canReceiveCounters(CounterType.getType(type));
}
});
@@ -452,7 +454,6 @@ public class CountersPutAi extends SpellAbilityAi {
// but try to do it in Main 2 then so that the AI has a chance to play creatures first.
if (list.isEmpty()
&& sa.hasParam("Planeswalker")
- && sa.getPayCosts() != null
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
&& sa.isTargetNumberValid()
&& sa.getTargets().getNumTargeted() == 0
@@ -487,7 +488,7 @@ public class CountersPutAi extends SpellAbilityAi {
}
return false;
}
-
+
// target loop
while (sa.canAddMoreTarget()) {
if (list.isEmpty()) {
@@ -557,7 +558,7 @@ public class CountersPutAi extends SpellAbilityAi {
return false;
}
- final int currCounters = cards.get(0).getCounters(CounterType.valueOf(type));
+ final int currCounters = cards.get(0).getCounters(CounterType.get(type));
// each non +1/+1 counter on the card is a 10% chance of not
// activating this ability.
@@ -573,11 +574,11 @@ public class CountersPutAi extends SpellAbilityAi {
}
boolean immediately = ComputerUtil.playImmediately(ai, sa);
-
+
if (abCost != null && !ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa, immediately)) {
return false;
}
-
+
if (immediately) {
return true;
}
@@ -611,7 +612,7 @@ public class CountersPutAi extends SpellAbilityAi {
final boolean divided = sa.hasParam("DividedAsYouChoose");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
- final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
+ final boolean isMandatoryTrigger = (sa.isTrigger() && !sa.isOptionalTrigger())
|| (sa.getRootAbility().isTrigger() && !sa.getRootAbility().isOptionalTrigger());
if (sa.usesTargeting()) {
@@ -691,12 +692,12 @@ public class CountersPutAi extends SpellAbilityAi {
final boolean divided = sa.hasParam("DividedAsYouChoose");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
int left = amount;
-
+
if (!sa.usesTargeting()) {
// No target. So must be defined
list = new CardCollection(AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa));
-
- if (amountStr.equals("X")
+
+ if (amountStr.equals("X")
&& !source.hasSVar("PayX") /* SubAbility on something that already had set PayX, e.g. Endless One ETB counters */
&& ((sa.hasParam(amountStr) && sa.getSVar(amountStr).equals("Count$xPaid")) || source.getSVar(amountStr).equals("Count$xPaid") )) {
@@ -704,7 +705,7 @@ public class CountersPutAi extends SpellAbilityAi {
SpellAbility testSa = sa;
int countX = 0;
int nonXGlyphs = 0;
- while (testSa != null && testSa.getPayCosts() != null && countX == 0) {
+ while (testSa != null && countX == 0) {
countX = testSa.getPayCosts().getTotalMana().countX();
nonXGlyphs = testSa.getPayCosts().getTotalMana().getGlyphCount() - countX;
testSa = testSa.getSubAbility();
@@ -726,12 +727,27 @@ public class CountersPutAi extends SpellAbilityAi {
source.setSVar("PayX", Integer.toString(payX));
}
-
+
if (!mandatory) {
// TODO - If Trigger isn't mandatory, when wouldn't we want to
// put a counter?
// things like Powder Keg, which are way too complex for the AI
}
+ } else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
+ // can only target opponent
+ List playerList = Lists.newArrayList(Iterables.filter(
+ sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
+
+ if (playerList.isEmpty() && mandatory) {
+ return false;
+ }
+
+ // try to choose player with less creatures
+ Player choice = Collections.min(playerList, PlayerPredicates.compareByZoneSize(ZoneType.Battlefield, CardPredicates.Presets.CREATURES));
+
+ if (choice != null) {
+ sa.getTargets().add(choice);
+ }
} else {
if (sa.isCurse()) {
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
@@ -838,7 +854,7 @@ public class CountersPutAi extends SpellAbilityAi {
List threatening = CardLists.filter(creats, new Predicate() {
@Override
public boolean apply(Card c) {
- return CombatUtil.canBlock(source, c, !isHaste)
+ return CombatUtil.canBlock(source, c, !isHaste)
&& (c.getNetToughness() > source.getNetPower() + tributeAmount || c.hasKeyword(Keyword.DEATHTOUCH));
}
});
@@ -873,24 +889,28 @@ public class CountersPutAi extends SpellAbilityAi {
}
@Override
- public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options) {
+ public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options, Map params) {
// used by Tribute, select player with lowest Life
// TODO add more logic using TributeAILogic
List list = Lists.newArrayList(options);
return Collections.min(list, PlayerPredicates.compareByLife());
}
-
+
@Override
- protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) {
+ protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
// Bolster does use this
// TODO need more or less logic there?
+ final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
+ final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
// no logic if there is no options or no to choice
if (!isOptional && Iterables.size(options) <= 1) {
return Iterables.getFirst(options, null);
}
- final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
+ final CounterType type = params.containsKey("CounterType") ? (CounterType)params.get("CounterType")
+ : CounterType.getType(sa.getParam("CounterType"));
+
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
@@ -907,7 +927,7 @@ public class CountersPutAi extends SpellAbilityAi {
return false;
if (ComputerUtilCard.isUselessCreature(ai, input))
return false;
- if (CounterType.M1M1.equals(type) && amount >= input.getNetToughness())
+ if (type.is(CounterEnumType.M1M1) && amount >= input.getNetToughness())
return true;
return ComputerUtil.isNegativeCounter(type, input);
}
@@ -931,6 +951,20 @@ public class CountersPutAi extends SpellAbilityAi {
CardCollection filtered = mine;
+ // Try to filter out keywords that we already have on cards
+ if (type.isKeywordCounter()) {
+ Keyword kw = Keyword.smartValueOf(type.getName());
+ final CardCollection doNotHaveKeyword = CardLists.filter(filtered, new Predicate() {
+ @Override
+ public boolean apply(Card card) {
+ return !card.hasKeyword(kw) && card.canBeTargetedBy(sa) && sa.canTarget(card);
+ }
+ });
+
+ if (doNotHaveKeyword.size() > 0)
+ filtered = doNotHaveKeyword;
+ }
+
final CardCollection notUseless = CardLists.filter(filtered, new Predicate() {
@Override
public boolean apply(Card input) {
@@ -945,26 +979,26 @@ public class CountersPutAi extends SpellAbilityAi {
}
// some special logic to reload Persist/Undying
- if (CounterType.P1P1.equals(type)) {
+ if (p1p1.equals(type)) {
final CardCollection persist = CardLists.filter(filtered, new Predicate() {
@Override
public boolean apply(Card input) {
if (!input.hasKeyword(Keyword.PERSIST))
return false;
- return input.getCounters(CounterType.M1M1) <= amount;
+ return input.getCounters(m1m1) <= amount;
}
});
if (!persist.isEmpty()) {
filtered = persist;
}
- } else if (CounterType.M1M1.equals(type)) {
+ } else if (m1m1.equals(type)) {
final CardCollection undying = CardLists.filter(filtered, new Predicate() {
@Override
public boolean apply(Card input) {
if (!input.hasKeyword(Keyword.UNDYING))
return false;
- return input.getCounters(CounterType.P1P1) <= amount && input.getNetToughness() > amount;
+ return input.getCounters(p1p1) <= amount && input.getNetToughness() > amount;
}
});
@@ -987,8 +1021,8 @@ public class CountersPutAi extends SpellAbilityAi {
if (e instanceof Card) {
Card c = (Card) e;
if (c.getController().isOpponentOf(ai)) {
- if (options.contains(CounterType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
- return CounterType.M1M1;
+ if (options.contains(CounterType.get(CounterEnumType.M1M1)) && !c.hasKeyword(Keyword.UNDYING)) {
+ return CounterType.get(CounterEnumType.M1M1);
}
for (CounterType type : options) {
if (ComputerUtil.isNegativeCounter(type, c)) {
@@ -1005,12 +1039,12 @@ public class CountersPutAi extends SpellAbilityAi {
} else if (e instanceof Player) {
Player p = (Player) e;
if (p.isOpponentOf(ai)) {
- if (options.contains(CounterType.POISON)) {
- return CounterType.POISON;
+ if (options.contains(CounterType.get(CounterEnumType.POISON))) {
+ return CounterType.get(CounterEnumType.POISON);
}
} else {
- if (options.contains(CounterType.EXPERIENCE)) {
- return CounterType.EXPERIENCE;
+ if (options.contains(CounterType.get(CounterEnumType.EXPERIENCE))) {
+ return CounterType.get(CounterEnumType.EXPERIENCE);
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java
index 6142b9c71ff..8c883637620 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAllAi.java
@@ -120,7 +120,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
//Check for cards that could profit from the ability
PhaseHandler phase = ai.getGame().getPhaseHandler();
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
- && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
+ && sa.getPayCosts().hasTapCost()
&& sa instanceof AbilitySub
&& (!phase.getNextTurn().equals(ai)
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java
index 4195a7924b7..a261270dec9 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java
@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
@@ -36,7 +36,7 @@ import java.util.Map;
*
* AbilityFactory_PutOrRemoveCountersAi class.
*
- *
+ *
* @author Forge
* @version $Id$
*/
@@ -44,7 +44,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@@ -75,7 +75,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
if (sa.hasParam("CounterType")) {
// currently only Jhoira's Timebug
- final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
+ final CounterType type = CounterType.getType(sa.getParam("CounterType"));
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
@@ -100,7 +100,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView depthsList = CardLists.filter(countersList,
- CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
+ CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterEnumType.ICE));
if (!depthsList.isEmpty()) {
sa.getTargets().add(depthsList.getFirst());
@@ -113,7 +113,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
CardCollection planeswalkerList = CardLists.filter(
CardLists.filterControlledBy(countersList, ai.getOpponents()),
CardPredicates.Presets.PLANESWALKERS,
- CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
+ CardPredicates.hasLessCounter(CounterEnumType.LOYALTY, amount));
if (!planeswalkerList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
@@ -123,7 +123,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
// do as M1M1 part
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
- CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
+ CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1));
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
if (!aiPersistList.isEmpty()) {
@@ -136,7 +136,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
}
// do as P1P1 part
- CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
+ CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.P1P1));
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
if (!aiUndyingList.isEmpty()) {
@@ -183,7 +183,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
* forge.game.spellability.SpellAbility, java.util.Map)
*/
@@ -199,18 +199,18 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
Card tgt = (Card) params.get("Target");
// planeswalker has high priority for loyalty counters
- if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
- return CounterType.LOYALTY;
+ if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) {
+ return CounterType.get(CounterEnumType.LOYALTY);
}
if (tgt.getController().isOpponentOf(ai)) {
// creatures with BaseToughness below or equal zero might be
// killed if their counters are removed
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
- if (options.contains(CounterType.P1P1)) {
- return CounterType.P1P1;
- } else if (options.contains(CounterType.M1M1)) {
- return CounterType.M1M1;
+ if (options.contains(CounterType.get(CounterEnumType.P1P1))) {
+ return CounterType.get(CounterEnumType.P1P1);
+ } else if (options.contains(CounterType.get(CounterEnumType.M1M1))) {
+ return CounterType.get(CounterEnumType.M1M1);
}
}
@@ -222,14 +222,14 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
}
} else {
// this counters are treat first to be removed
- if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
+ if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
- return CounterType.ICE;
+ return CounterType.get(CounterEnumType.ICE);
}
- } else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.P1P1)) {
- return CounterType.P1P1;
- } else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.M1M1)) {
- return CounterType.M1M1;
+ } else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
+ return CounterType.get(CounterEnumType.P1P1);
+ } else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) {
+ return CounterType.get(CounterEnumType.M1M1);
}
// fallback logic, select positive counter to add more
@@ -246,7 +246,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see
* forge.ai.SpellAbilityAi#chooseBinary(forge.game.player.PlayerController.
* BinaryChoiceType, forge.game.spellability.SpellAbility, java.util.Map)
@@ -262,19 +262,19 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
if (tgt.getController().isOpponentOf(ai)) {
- if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
+ if (type.is(CounterEnumType.LOYALTY) && tgt.isPlaneswalker()) {
return false;
}
return ComputerUtil.isNegativeCounter(type, tgt);
} else {
- if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
+ if (type.is(CounterEnumType.ICE) && "Dark Depths".equals(tgt.getName())) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
return false;
}
- } else if (type.equals(CounterType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
+ } else if (type.is(CounterEnumType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
return false;
- } else if (type.equals(CounterType.P1P1) && tgt.hasKeyword(Keyword.UNDYING)) {
+ } else if (type.is(CounterEnumType.P1P1) && tgt.hasKeyword(Keyword.UNDYING)) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java
index 24a0ff0fa9d..2c4ad3bbfe0 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java
@@ -33,7 +33,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler)
@@ -50,7 +50,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see
* forge.ai.SpellAbilityAi#checkPhaseRestrictions(forge.game.player.Player,
* forge.game.spellability.SpellAbility, forge.game.phase.PhaseHandler,
@@ -68,7 +68,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see forge.ai.SpellAbilityAi#checkApiLogic(forge.game.player.Player,
* forge.game.spellability.SpellAbility)
*/
@@ -82,7 +82,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
if (!type.matches("Any") && !type.matches("All")) {
- final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
+ final int currCounters = sa.getHostCard().getCounters(CounterType.getType(type));
if (currCounters < 1) {
return false;
}
@@ -119,7 +119,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
- CardPredicates.hasCounter(CounterType.ICE, 3));
+ CardPredicates.hasCounter(CounterEnumType.ICE, 3));
if (!depthsList.isEmpty()) {
sa.getTargets().add(depthsList.getFirst());
@@ -132,7 +132,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS,
- CardPredicates.hasCounter(CounterType.LOYALTY, 5));
+ CardPredicates.hasCounter(CounterEnumType.LOYALTY, 5));
if (!planeswalkerList.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
@@ -159,11 +159,11 @@ public class CountersRemoveAi extends SpellAbilityAi {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
- CardPredicates.hasCounter(CounterType.ICE));
+ CardPredicates.hasCounter(CounterEnumType.ICE));
if (!depthsList.isEmpty()) {
Card depth = depthsList.getFirst();
- int ice = depth.getCounters(CounterType.ICE);
+ int ice = depth.getCounters(CounterEnumType.ICE);
if (amount >= ice) {
sa.getTargets().add(depth);
if (xPay) {
@@ -180,7 +180,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
CardCollection planeswalkerList = CardLists.filter(list,
Predicates.and(CardPredicates.Presets.PLANESWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
- CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
+ CardPredicates.hasLessCounter(CounterEnumType.LOYALTY, amount));
if (!planeswalkerList.isEmpty()) {
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
@@ -196,7 +196,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
// do as M1M1 part
CardCollection aiList = CardLists.filterControlledBy(list, ai);
- CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
+ CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1));
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
if (!aiPersistList.isEmpty()) {
@@ -209,7 +209,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
// do as P1P1 part
- CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasLessCounter(CounterType.P1P1, amount));
+ CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasLessCounter(CounterEnumType.P1P1, amount));
CardCollection aiUndyingList = CardLists.getKeyword(aiP1P1List, Keyword.UNDYING);
if (!aiUndyingList.isEmpty()) {
@@ -220,7 +220,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
// remove P1P1 counters from opposing creatures
CardCollection oppP1P1List = CardLists.filter(list,
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
- CardPredicates.hasCounter(CounterType.P1P1));
+ CardPredicates.hasCounter(CounterEnumType.P1P1));
if (!oppP1P1List.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
return true;
@@ -244,7 +244,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
// no special amount for that one yet
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
CardCollection aiList = CardLists.filterControlledBy(list, ai);
- aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
+ aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1, amount));
CardCollection aiPersist = CardLists.getKeyword(aiList, Keyword.PERSIST);
if (!aiPersist.isEmpty()) {
@@ -263,7 +263,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
// no special amount for that one yet
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
- list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
+ list = CardLists.filter(list, CardPredicates.hasCounter(CounterEnumType.P1P1, amount));
// currently only logic for Bloodcrazed Hoplite, but add logic for
// targeting ai creatures too
@@ -309,12 +309,12 @@ public class CountersRemoveAi extends SpellAbilityAi {
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
}
- CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
+ CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterEnumType.TIME, amount));
if (!timeList.isEmpty()) {
Card best = ComputerUtilCard.getBestAI(timeList);
- int timeCount = best.getCounters(CounterType.TIME);
+ int timeCount = best.getCounters(CounterEnumType.TIME);
sa.getTargets().add(best);
if (xPay) {
source.setSVar("PayX", Integer.toString(timeCount));
@@ -335,7 +335,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
CardCollection outlastCreats = CardLists.filter(list, CardPredicates.hasKeyword(Keyword.OUTLAST));
if (!outlastCreats.isEmpty()) {
// outlast cards often benefit from having +1/+1 counters, try not to remove last one
- CardCollection betterTargets = CardLists.filter(outlastCreats, CardPredicates.hasCounter(CounterType.P1P1, 2));
+ CardCollection betterTargets = CardLists.filter(outlastCreats, CardPredicates.hasCounter(CounterEnumType.P1P1, 2));
if (!betterTargets.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getWorstAI(betterTargets));
@@ -363,7 +363,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see forge.ai.SpellAbilityAi#chooseNumber(forge.game.player.Player,
* forge.game.spellability.SpellAbility, int, int, java.util.Map)
*/
@@ -377,8 +377,8 @@ public class CountersRemoveAi extends SpellAbilityAi {
if (targetCard.getController().isOpponentOf(player)) {
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
} else {
- if (targetCard.hasKeyword(Keyword.UNDYING) && type == CounterType.P1P1
- && targetCard.getCounters(CounterType.P1P1) >= max) {
+ if (targetCard.hasKeyword(Keyword.UNDYING) && type.is(CounterEnumType.P1P1)
+ && targetCard.getCounters(CounterEnumType.P1P1) >= max) {
return max;
}
@@ -387,9 +387,9 @@ public class CountersRemoveAi extends SpellAbilityAi {
} else if (target instanceof Player) {
Player targetPlayer = (Player) target;
if (targetPlayer.isOpponentOf(player)) {
- return !type.equals(CounterType.POISON) ? max : min;
+ return !type.equals(CounterEnumType.POISON) ? max : min;
} else {
- return type.equals(CounterType.POISON) ? max : min;
+ return type.equals(CounterEnumType.POISON) ? max : min;
}
}
@@ -398,7 +398,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
/*
* (non-Javadoc)
- *
+ *
* @see forge.ai.SpellAbilityAi#chooseCounterType(java.util.List,
* forge.game.spellability.SpellAbility, java.util.Map)
*/
@@ -415,7 +415,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
if (targetCard.getController().isOpponentOf(ai)) {
// if its a Planeswalker try to remove Loyality first
if (targetCard.isPlaneswalker()) {
- return CounterType.LOYALTY;
+ return CounterType.get(CounterEnumType.LOYALTY);
}
for (CounterType type : options) {
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
@@ -423,10 +423,10 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
}
} else {
- if (options.contains(CounterType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
- return CounterType.M1M1;
- } else if (options.contains(CounterType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
- return CounterType.P1P1;
+ if (options.contains(CounterType.get(CounterEnumType.M1M1)) && targetCard.hasKeyword(Keyword.PERSIST)) {
+ return CounterType.get(CounterEnumType.M1M1);
+ } else if (options.contains(CounterType.get(CounterEnumType.P1P1)) && targetCard.hasKeyword(Keyword.UNDYING)) {
+ return CounterType.get(CounterEnumType.P1P1);
}
for (CounterType type : options) {
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
@@ -438,13 +438,13 @@ public class CountersRemoveAi extends SpellAbilityAi {
Player targetPlayer = (Player) target;
if (targetPlayer.isOpponentOf(ai)) {
for (CounterType type : options) {
- if (!type.equals(CounterType.POISON)) {
+ if (!type.equals(CounterEnumType.POISON)) {
return type;
}
}
} else {
for (CounterType type : options) {
- if (type.equals(CounterType.POISON)) {
+ if (type.equals(CounterEnumType.POISON)) {
return type;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java
index cbede1c1a11..79e83ac65a4 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java
@@ -6,7 +6,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
-import forge.game.card.CounterType;
+import forge.game.card.CounterEnumType;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
@@ -39,7 +39,7 @@ public class DamageAllAi extends SpellAbilityAi {
if (!ai.getGame().getStack().isEmpty()) {
return false;
}
-
+
int x = -1;
final String damage = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
@@ -50,10 +50,9 @@ public class DamageAllAi extends SpellAbilityAi {
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
}
if (damage.equals("ChosenX")) {
- x = source.getCounters(CounterType.LOYALTY);
+ x = source.getCounters(CounterEnumType.LOYALTY);
}
if (x == -1) {
- Player bestOpp = determineOppToKill(ai, sa, source, dmg);
if (determineOppToKill(ai, sa, source, dmg) != null) {
// we already know we can kill a player, so go for it
return true;
@@ -138,7 +137,7 @@ public class DamageAllAi extends SpellAbilityAi {
}
int minGain = 200; // The minimum gain in destroyed creatures
- if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
+ if (sa.getPayCosts().isReusuableResource()) {
if (computerList.isEmpty()) {
minGain = 10; // nothing to lose
// no creatures to lose and player can be damaged
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 797c18d441f..936e2187357 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
@@ -46,9 +46,9 @@ public class DamageDealAi extends DamageAiBase {
if ("MadSarkhanDigDmg".equals(logic)) {
return SpecialCardAi.SarkhanTheMad.considerDig(ai, sa);
}
-
+
if (damage.equals("X") && sa.getSVar(damage).equals("Count$ChosenNumber")) {
- int energy = ai.getCounters(CounterType.ENERGY);
+ int energy = ai.getCounters(CounterEnumType.ENERGY);
for (SpellAbility s : source.getSpellAbilities()) {
if ("PayEnergy".equals(s.getParam("AILogic"))) {
energy += AbilityUtils.calculateAmount(source, s.getParam("CounterNum"), sa);
@@ -145,7 +145,7 @@ public class DamageDealAi extends DamageAiBase {
if (sourceName.equals("Crater's Claws") && ai.hasFerocious()) {
dmg += 2;
}
-
+
String logic = sa.getParamOrDefault("AILogic", "");
if ("DiscardLands".equals(logic)) {
dmg = 2;
@@ -165,7 +165,7 @@ public class DamageDealAi extends DamageAiBase {
List wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source);
dmg = Aggregates.sum(wolves, CardPredicates.Accessors.fnGetNetPower);
} else if ("Triskelion".equals(logic)) {
- final int n = source.getCounters(CounterType.P1P1);
+ final int n = source.getCounters(CounterEnumType.P1P1);
if (n > 0) {
if (ComputerUtil.playImmediately(ai, sa)) {
/*
@@ -196,9 +196,9 @@ public class DamageDealAi extends DamageAiBase {
}
return false;
}
-
+
if (sourceName.equals("Sorin, Grim Nemesis")) {
- int loyalty = source.getCounters(CounterType.LOYALTY);
+ int loyalty = source.getCounters(CounterEnumType.LOYALTY);
for (; loyalty > 0; loyalty--) {
if (this.damageTargetAI(ai, sa, loyalty, false)) {
dmg = ComputerUtilCombat.getEnoughDamageToKill(sa.getTargetCard(), loyalty, source, false, false);
@@ -228,7 +228,7 @@ public class DamageDealAi extends DamageAiBase {
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source)) {
return false;
}
-
+
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
@@ -285,7 +285,7 @@ public class DamageDealAi extends DamageAiBase {
}
}
- if ("XCountersDamage".equals(logic) && sa.getPayCosts() != null) {
+ if ("XCountersDamage".equals(logic)) {
// Check to ensure that we have enough counters to remove per the defined PayX
for (CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {
@@ -309,7 +309,7 @@ public class DamageDealAi extends DamageAiBase {
*
* dealDamageChooseTgtC.
*
- *
+ *
* @param d
* a int.
* @param noPrevention
@@ -445,11 +445,11 @@ public class DamageDealAi extends DamageAiBase {
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
int bestScore = 0;
for (Card pw : pws) {
- int curLoyalty = pw.getCounters(CounterType.LOYALTY);
+ int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
int pwScore = curLoyalty * 10;
for (SpellAbility sa : pw.getSpellAbilities()) {
- if (sa.hasParam("Ultimate") && sa.getPayCosts() != null) {
+ if (sa.hasParam("Ultimate")) {
Integer loyaltyCost = 0;
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
if (remLoyalty != null) {
@@ -478,7 +478,7 @@ public class DamageDealAi extends DamageAiBase {
int bestScore = Integer.MAX_VALUE;
for (Card pw : pws) {
- int curLoyalty = pw.getCounters(CounterType.LOYALTY);
+ int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
if (curLoyalty < bestScore) {
bestScore = curLoyalty;
@@ -515,7 +515,7 @@ public class DamageDealAi extends DamageAiBase {
*
* damageTargetAI.
*
- *
+ *
* @param saMe
* a {@link forge.game.spellability.SpellAbility} object.
* @param dmg
@@ -543,7 +543,7 @@ public class DamageDealAi extends DamageAiBase {
*
* damageChoosingTargets.
*
- *
+ *
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param tgt
@@ -587,7 +587,7 @@ public class DamageDealAi extends DamageAiBase {
if (tgt.getMaxTargets(source, sa) <= 0 && !logic.equals("AssumeAtLeastOneTarget")) {
return false;
}
-
+
immediately |= ComputerUtil.playImmediately(ai, sa);
if (!(sa.getParent() != null && sa.getParent().isTargetNumberValid())) {
@@ -623,7 +623,7 @@ public class DamageDealAi extends DamageAiBase {
continue;
}
final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(humanCreature, dmg, source, false, noPrevention);
- if (assignedDamage <= dmg
+ if (assignedDamage <= dmg
&& humanCreature.getShieldCount() == 0 && !ComputerUtil.canRegenerate(humanCreature.getController(), humanCreature)) {
tcs.add(humanCreature);
tgt.addDividedAllocation(humanCreature, assignedDamage);
@@ -756,7 +756,7 @@ public class DamageDealAi extends DamageAiBase {
break;
}
}
-
+
} else if (tgt.canTgtCreature() || tgt.canTgtPlaneswalker()) {
final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, mandatory);
if (c != null) {
@@ -794,8 +794,7 @@ public class DamageDealAi extends DamageAiBase {
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
- || sa.getPayCosts() == null || immediately
- || this.shouldTgtP(ai, sa, dmg, noPrevention)) &&
+ || immediately || shouldTgtP(ai, sa, dmg, noPrevention)) &&
(!avoidTargetP(ai, sa))) {
tcs.add(enemy);
if (divided) {
@@ -826,8 +825,8 @@ public class DamageDealAi extends DamageAiBase {
*
* damageChooseNontargeted.
*
- * @param ai
- *
+ * @param ai
+ *
* @param saMe
* a {@link forge.game.spellability.SpellAbility} object.
* @param dmg
@@ -882,7 +881,7 @@ public class DamageDealAi extends DamageAiBase {
*
* damageChooseRequiredTargets.
*
- *
+ *
* @param sa
* a {@link forge.game.spellability.SpellAbility} object.
* @param tgt
@@ -1007,7 +1006,7 @@ public class DamageDealAi extends DamageAiBase {
// If I can kill my target by paying less mana, do it
int actualPay = 0;
final boolean noPrevention = sa.hasParam("NoPrevention");
-
+
//target is a player
if (!sa.getTargets().isTargetingAnyCard()) {
actualPay = dmg;
@@ -1038,15 +1037,15 @@ public class DamageDealAi extends DamageAiBase {
Player opponent = ai.getOpponents().min(PlayerPredicates.compareByLife());
- // TODO: somehow account for the possible cost reduction?
+ // TODO: somehow account for the possible cost reduction?
int dmg = ComputerUtilMana.determineLeftoverMana(sa, ai, saTgt.getParam("XColor"));
-
+
while (!ComputerUtilMana.canPayManaCost(sa, ai, dmg) && dmg > 0) {
// TODO: ideally should never get here, currently put here as a precaution for complex mana base cases where the miscalculation might occur. Will remove later if it proves to never trigger.
dmg--;
System.out.println("Warning: AI could not pay mana cost for a XLifeDrain logic spell. Reducing X value to "+dmg);
}
-
+
// set the color map for black X for the purpose of Soul Burn
// TODO: somehow generalize this calculation to allow other potential similar cards to function in the future
if ("Soul Burn".equals(sourceName)) {
@@ -1067,7 +1066,7 @@ public class DamageDealAi extends DamageAiBase {
int toughness = c.getNetToughness();
boolean canDie = !(c.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(c.getController(), c));
- // Currently will target creatures with toughness 3+ (or power 5+)
+ // Currently will target creatures with toughness 3+ (or power 5+)
// and only if the creature can actually die, do not "underdrain"
// unless the creature has high power
if (canDie && toughness <= dmg && ((toughness == dmg && toughness >= 3) || power >= 5)) {
@@ -1126,8 +1125,7 @@ public class DamageDealAi extends DamageAiBase {
continue;
}
// currently works only with cards that don't have additional costs (only mana is supported)
- if (ab.getPayCosts() != null
- && (ab.getPayCosts().hasNoManaCost() || ab.getPayCosts().hasOnlySpecificCostType(CostPartMana.class))) {
+ if (ab.getPayCosts().hasNoManaCost() || ab.getPayCosts().hasOnlySpecificCostType(CostPartMana.class)) {
String dmgDef = "0";
if (ab.getApi() == ApiType.DealDamage) {
dmgDef = ab.getParamOrDefault("NumDmg", "0");
@@ -1151,7 +1149,7 @@ public class DamageDealAi extends DamageAiBase {
}
// FIXME: should it also check restrictions for targeting players?
- ManaCost costSa = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : ManaCost.NO_COST;
+ ManaCost costSa = sa.getPayCosts().getTotalMana();
ManaCost costAb = ab.getPayCosts().getTotalMana(); // checked for null above
ManaCost total = ManaCost.combine(costSa, costAb);
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
index fd7632bc44d..8c3855385e6 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
@@ -101,7 +101,7 @@ public class DestroyAi extends SpellAbilityAi {
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
- if (source.getCounters(CounterType.LOYALTY) < minLoyalty) {
+ if (source.getCounters(CounterEnumType.LOYALTY) < minLoyalty) {
return false;
}
} else if ("Polymorph".equals(logic)) {
@@ -161,7 +161,7 @@ public class DestroyAi extends SpellAbilityAi {
return false;
}
//Check for undying
- return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterType.P1P1) > 0);
+ return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterEnumType.P1P1) > 0);
}
});
}
@@ -231,7 +231,7 @@ public class DestroyAi extends SpellAbilityAi {
}
if ("Pongify".equals(logic)) {
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
- if (token == null) {
+ if (token == null || !token.isCreature() || token.getNetToughness() < 1) {
return true; // becomes Terminate
} else {
if (source.getGame().getPhaseHandler().getPhase()
@@ -256,7 +256,7 @@ public class DestroyAi extends SpellAbilityAi {
}
//option to hold removal instead only applies for single targeted removal
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
- if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
+ if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
return false;
}
}
@@ -277,6 +277,7 @@ public class DestroyAi extends SpellAbilityAi {
SpellAbility sp = aura.getFirstSpellAbility();
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
&& aura.getController() != ai && sa.canTarget(aura)) {
+ list.remove(choice);
choice = aura;
}
}
@@ -387,7 +388,7 @@ public class DestroyAi extends SpellAbilityAi {
if (CardLists.getNotType(list, "Creature").isEmpty()) {
if (!sa.getUniqueTargets().isEmpty() && sa.getParent().getApi() == ApiType.Destroy
&& sa.getUniqueTargets().get(0) instanceof Card) {
- // basic ai for Diaochan
+ // basic ai for Diaochan
c = (Card) sa.getUniqueTargets().get(0);
} else {
c = ComputerUtilCard.getWorstCreatureAI(list);
@@ -412,7 +413,7 @@ public class DestroyAi extends SpellAbilityAi {
Player tgtPlayer = tgtLand.getController();
int oppLandsOTB = tgtPlayer.getLandsInPlay().size();
-
+
// AI profile-dependent properties
AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK);
@@ -435,7 +436,7 @@ public class DestroyAi extends SpellAbilityAi {
// Non-basic lands are currently not ranked in any way in ComputerUtilCard#getBestLandAI, so if a non-basic land is best target,
// consider killing it off unless there's too much potential tempo loss.
- // TODO: actually rank non-basics in that method and then kill off the potentially dangerous (manlands, Valakut) or lucrative
+ // TODO: actually rank non-basics in that method and then kill off the potentially dangerous (manlands, Valakut) or lucrative
// (dual/triple mana that opens access to a certain color) lands
boolean nonBasicTgt = !tgtLand.isBasicLand();
@@ -447,7 +448,7 @@ public class DestroyAi extends SpellAbilityAi {
boolean isHighPriority = highPriorityIfNoLandDrop && oppSkippedLandDrop;
boolean timingCheck = canManaLock || canColorLock || nonBasicTgt;
- boolean tempoCheck = numLandsOTB >= amountNoTempoCheck
+ boolean tempoCheck = numLandsOTB >= amountNoTempoCheck
|| ((numLandsInHand >= amountLandsInHand || isHighPriority) && ((numLandsInHand + numLandsOTB >= amountNoTimingCheck) || timingCheck));
// For Ghost Quarter, only use it if you have either more lands in play than your opponent
diff --git a/forge-ai/src/main/java/forge/ai/ability/DigAi.java b/forge-ai/src/main/java/forge/ai/ability/DigAi.java
index b8b13d18fd3..7099250d925 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DigAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DigAi.java
@@ -1,5 +1,7 @@
package forge.ai.ability;
+import java.util.Map;
+
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
@@ -132,7 +134,7 @@ public class DigAi extends SpellAbilityAi {
}
@Override
- public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable valid, boolean isOptional, Player relatedPlayer) {
+ public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable valid, boolean isOptional, Player relatedPlayer, Map params) {
if ("DigForCreature".equals(sa.getParam("AILogic"))) {
Card bestChoice = ComputerUtilCard.getBestCreatureAI(valid);
if (bestChoice == null) {
@@ -163,7 +165,7 @@ public class DigAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
*/
@Override
- public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options) {
+ public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options, Map params) {
// an opponent choose a card from
return Iterables.getFirst(options, null);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
index 59438a2fb52..4047d12ee77 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
@@ -25,6 +25,7 @@ import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
+import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.cost.*;
import forge.game.phase.PhaseHandler;
@@ -262,22 +263,20 @@ public class DrawAi extends SpellAbilityAi {
// Draw up to max hand size but leave at least 3 in library
numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
- if (sa.getPayCosts() != null) {
- if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
- // [Necrologia, Pay X Life : Draw X Cards]
- // Don't draw more than what's "safe" and don't risk a near death experience
- // Maybe would be better to check for "serious danger" and take more risk?
- while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
- numCards--;
- }
- } else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
- // [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
- // TODO: Add special logic to limit/otherwise modify the ChosenX value here
+ if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
+ // [Necrologia, Pay X Life : Draw X Cards]
+ // Don't draw more than what's "safe" and don't risk a near death experience
+ // Maybe would be better to check for "serious danger" and take more risk?
+ while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
+ numCards--;
+ }
+ } else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
+ // [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
+ // TODO: Add special logic to limit/otherwise modify the ChosenX value here
- // Skip this ability if nothing is to be chosen for sacrifice
- if (numCards <= 0) {
- return false;
- }
+ // Skip this ability if nothing is to be chosen for sacrifice
+ if (numCards <= 0) {
+ return false;
}
}
@@ -350,7 +349,7 @@ public class DrawAi extends SpellAbilityAi {
}
// try to make opponent lose to poison
// currently only Caress of Phyrexia
- if (getPoison != null && oppA.canReceiveCounters(CounterType.POISON)) {
+ if (getPoison != null && oppA.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
if (oppA.getPoisonCounters() + numCards > 9) {
sa.getTargets().add(oppA);
return true;
@@ -394,7 +393,7 @@ public class DrawAi extends SpellAbilityAi {
}
}
- if (getPoison != null && ai.canReceiveCounters(CounterType.POISON)) {
+ if (getPoison != null && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
if (numCards + ai.getPoisonCounters() >= 8) {
aiTarget = false;
}
@@ -453,7 +452,7 @@ public class DrawAi extends SpellAbilityAi {
}
// ally would lose because of poison
- if (getPoison != null && ally.canReceiveCounters(CounterType.POISON)) {
+ if (getPoison != null && ally.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
if (ally.getPoisonCounters() + numCards > 9) {
continue;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java
index 609aec7c92d..77a20350e69 100644
--- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java
@@ -113,7 +113,7 @@ public class EffectAi extends SpellAbilityAi {
} else if (logic.equals("SpellCopy")) {
// fetch Instant or Sorcery and AI has reason to play this turn
// does not try to get itself
- final ManaCost costSa = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : ManaCost.NO_COST;
+ final ManaCost costSa = sa.getPayCosts().getTotalMana();
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate() {
@Override
public boolean apply(final Card c) {
@@ -135,7 +135,7 @@ public class EffectAi extends SpellAbilityAi {
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(ab);
// see if we can pay both for this spell and for the Effect spell we're considering
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
- ManaCost costAb = ab.getPayCosts() != null ? ab.getPayCosts().getTotalMana() : ManaCost.NO_COST;
+ ManaCost costAb = ab.getPayCosts().getTotalMana();
ManaCost total = ManaCost.combine(costSa, costAb);
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
// can we pay both costs?
diff --git a/forge-ai/src/main/java/forge/ai/ability/EncodeAi.java b/forge-ai/src/main/java/forge/ai/ability/EncodeAi.java
index fcfe5c957ba..53587f59cd6 100644
--- a/forge-ai/src/main/java/forge/ai/ability/EncodeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/EncodeAi.java
@@ -18,6 +18,7 @@
package forge.ai.ability;
import java.util.List;
+import java.util.Map;
import com.google.common.base.Predicate;
@@ -84,7 +85,7 @@ public final class EncodeAi extends SpellAbilityAi {
* forge.game.player.Player)
*/
@Override
- public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) {
+ public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
return chooseCard(ai, options, isOptional);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/InvestigateAi.java b/forge-ai/src/main/java/forge/ai/ability/InvestigateAi.java
new file mode 100644
index 00000000000..1ae9da74c7b
--- /dev/null
+++ b/forge-ai/src/main/java/forge/ai/ability/InvestigateAi.java
@@ -0,0 +1,27 @@
+package forge.ai.ability;
+
+
+import forge.ai.SpellAbilityAi;
+import forge.game.phase.PhaseHandler;
+import forge.game.phase.PhaseType;
+import forge.game.player.Player;
+import forge.game.player.PlayerActionConfirmMode;
+import forge.game.spellability.SpellAbility;
+
+public class InvestigateAi extends SpellAbilityAi {
+ /* (non-Javadoc)
+ * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
+ */
+ @Override
+ protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
+ PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
+
+ return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer;
+ }
+
+ @Override
+ public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
+ return true;
+ }
+}
+
diff --git a/forge-ai/src/main/java/forge/ai/ability/LegendaryRuleAi.java b/forge-ai/src/main/java/forge/ai/ability/LegendaryRuleAi.java
index 87e9d2d9464..4ad913b7a25 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LegendaryRuleAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LegendaryRuleAi.java
@@ -1,9 +1,11 @@
package forge.ai.ability;
+import java.util.Map;
+
import com.google.common.collect.Iterables;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
-import forge.game.card.CounterType;
+import forge.game.card.CounterEnumType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -23,7 +25,7 @@ public class LegendaryRuleAi extends SpellAbilityAi {
@Override
- public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) {
+ public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
// Choose a single legendary/planeswalker card to keep
Card firstOption = Iterables.getFirst(options, null);
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
@@ -38,16 +40,16 @@ public class LegendaryRuleAi extends SpellAbilityAi {
if (firstOption.getName().equals("Dark Depths")) {
Card best = firstOption;
for (Card c : options) {
- if (c.getCounters(CounterType.ICE) < best.getCounters(CounterType.ICE)) {
+ if (c.getCounters(CounterEnumType.ICE) < best.getCounters(CounterEnumType.ICE)) {
best = c;
}
}
return best;
- } else if (firstOption.getCounters(CounterType.KI) > 0) {
+ } else if (firstOption.getCounters(CounterEnumType.KI) > 0) {
// Extra Rule for KI counter
Card best = firstOption;
for (Card c : options) {
- if (c.getCounters(CounterType.KI) > best.getCounters(CounterType.KI)) {
+ if (c.getCounters(CounterEnumType.KI) > best.getCounters(CounterEnumType.KI)) {
best = c;
}
}
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 41751f85882..5ad2c283477 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeGainAi.java
@@ -88,7 +88,6 @@ public class LifeGainAi extends SpellAbilityAi {
if (lifeCritical
&& sa.isAbility()
&& sa.getHostCard() != null && sa.getHostCard().isCreature()
- && sa.getPayCosts() != null
&& (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
if (!game.getStack().isEmpty()) {
SpellAbility saTop = game.getStack().peekAbility();
diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java
index cc4d851bd9b..2afc4b47137 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java
@@ -5,7 +5,7 @@ import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
-import forge.game.card.CounterType;
+import forge.game.card.CounterEnumType;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -130,7 +130,7 @@ public class LifeSetAi extends SpellAbilityAi {
}
if (sourceName.equals("Eternity Vessel")
- && (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterType.CHARGE) == 0))) {
+ && (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterEnumType.CHARGE) == 0))) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
index 60eaeef180f..80f7b916f11 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
@@ -81,7 +81,7 @@ public class ManaEffectAi extends SpellAbilityAi {
return true; // handled elsewhere, does not meet the standard requirements
}
- return sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
+ return sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa);
// return super.checkApiLogic(ai, sa);
}
@@ -119,8 +119,8 @@ public class ManaEffectAi extends SpellAbilityAi {
int numCounters = 0;
int manaSurplus = 0;
if ("XChoice".equals(host.getSVar("X"))
- && sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
- CounterType ctrType = CounterType.KI; // Petalmane Baku
+ && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
+ CounterType ctrType = CounterType.get(CounterEnumType.KI); // Petalmane Baku
for (CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {
ctrType = ((CostRemoveCounter)part).counter;
diff --git a/forge-ai/src/main/java/forge/ai/ability/MillAi.java b/forge-ai/src/main/java/forge/ai/ability/MillAi.java
index fba432c9d48..52f0e8cda25 100644
--- a/forge-ai/src/main/java/forge/ai/ability/MillAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/MillAi.java
@@ -1,15 +1,10 @@
package forge.ai.ability;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
-
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilMana;
+import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -24,6 +19,11 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
public class MillAi extends SpellAbilityAi {
@Override
@@ -196,6 +196,10 @@ public class MillAi extends SpellAbilityAi {
*/
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
+ if ("TimmerianFiends".equals(sa.getParam("AILogic"))) {
+ return SpecialCardAi.TimmerianFiends.consider(player, sa);
+ }
+
return true;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java
index 925f8ce02a1..fe921f05e72 100644
--- a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java
@@ -18,6 +18,7 @@ import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import java.util.List;
+import java.util.Map;
public class MustBlockAi extends SpellAbilityAi {
@@ -167,7 +168,7 @@ public class MustBlockAi extends SpellAbilityAi {
@Override
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional,
- Player targetedPlayer) {
+ Player targetedPlayer, Map params) {
final Card host = sa.getHostCard();
Card attacker = host;
diff --git a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java
index 96c22f1d519..7168f4c911e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java
@@ -20,6 +20,7 @@ import forge.game.zone.ZoneType;
import forge.util.MyRandom;
import java.util.List;
+import java.util.Map;
public class PlayAi extends SpellAbilityAi {
@@ -84,11 +85,11 @@ public class PlayAi extends SpellAbilityAi {
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
} else if (logic.startsWith("NeedsChosenCard")) {
int minCMC = 0;
- if (sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null) {
- minCMC = sa.getPayCosts().getCostMana().getMana().getCMC();
+ if (sa.getPayCosts().getCostMana() != null) {
+ minCMC = sa.getPayCosts().getTotalMana().getCMC();
}
validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC));
- return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null) != null;
+ return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null, null) != null;
}
if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) {
@@ -142,8 +143,7 @@ public class PlayAi extends SpellAbilityAi {
*/
@Override
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable options,
- final boolean isOptional,
- Player targetedPlayer) {
+ final boolean isOptional, Player targetedPlayer, Map params) {
List tgtCards = CardLists.filter(options, new Predicate() {
@Override
public boolean apply(final Card c) {
@@ -156,9 +156,7 @@ public class PlayAi extends SpellAbilityAi {
if (sa.hasParam("WithoutManaCost")) {
// Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0.
if (!(spell instanceof SpellPermanent)) {
- if (spell.getPayCosts() != null
- && spell.getPayCosts().getCostMana() != null
- && spell.getPayCosts().getCostMana().getMana().countX() > 0) {
+ if (spell.getPayCosts().getTotalMana().countX() > 0) {
continue;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/PoisonAi.java b/forge-ai/src/main/java/forge/ai/ability/PoisonAi.java
index 0eba3082f50..5761c79832c 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PoisonAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PoisonAi.java
@@ -5,6 +5,7 @@ import com.google.common.base.Predicate;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
+import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
@@ -59,7 +60,7 @@ public class PoisonAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) {
return tgtPlayer(ai, sa, mandatory);
- } else if (mandatory || !ai.canReceiveCounters(CounterType.POISON)) {
+ } else if (mandatory || !ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
// mandatory or ai is uneffected
return true;
} else {
@@ -92,7 +93,7 @@ public class PoisonAi extends SpellAbilityAi {
public boolean apply(Player input) {
if (input.cantLose()) {
return false;
- } else if (!input.canReceiveCounters(CounterType.POISON)) {
+ } else if (!input.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
return false;
}
return true;
@@ -113,7 +114,7 @@ public class PoisonAi extends SpellAbilityAi {
if (tgts.isEmpty()) {
if (mandatory) {
// AI is uneffected
- if (ai.canBeTargetedBy(sa) && ai.canReceiveCounters(CounterType.POISON)) {
+ if (ai.canBeTargetedBy(sa) && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
sa.getTargets().add(ai);
return true;
}
@@ -127,7 +128,7 @@ public class PoisonAi extends SpellAbilityAi {
if (input.cantLose()) {
return true;
}
- return !input.canReceiveCounters(CounterType.POISON);
+ return !input.canReceiveCounters(CounterType.get(CounterEnumType.POISON));
}
});
diff --git a/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java b/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java
index 310fa6e32af..79e0f5bcbc7 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ProtectAi.java
@@ -202,7 +202,7 @@ public class ProtectAi extends SpellAbilityAi {
if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare
// attack/block
- if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
+ if (sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getHostCard());
diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java
index ce161caee80..ced79a57318 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java
@@ -150,7 +150,7 @@ public class PumpAi extends PumpAiBase {
}
final String counterType = moveSA.getParam("CounterType");
- final CounterType cType = "Any".equals(counterType) ? null : CounterType.valueOf(counterType);
+ final CounterType cType = "Any".equals(counterType) ? null : CounterType.getType(counterType);
final PhaseHandler ph = game.getPhaseHandler();
if (ph.inCombat() && ph.getPlayerTurn().isOpponentOf(ai)) {
@@ -185,7 +185,7 @@ public class PumpAi extends PumpAiBase {
// cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
- if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
+ if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|| card.isToken();
}
@@ -235,7 +235,7 @@ public class PumpAi extends PumpAiBase {
// cant use substract on Copy
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
- if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
+ if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|| card.isToken();
}
@@ -402,7 +402,7 @@ public class PumpAi extends PumpAiBase {
if ("DebuffForXCounters".equals(sa.getParam("AILogic")) && sa.getTargetCard() != null) {
// e.g. Skullmane Baku
- CounterType ctrType = CounterType.KI;
+ CounterType ctrType = CounterType.get(CounterEnumType.KI);
for (CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {
ctrType = ((CostRemoveCounter)part).counter;
@@ -515,7 +515,7 @@ public class PumpAi extends PumpAiBase {
if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare
// attack/block
- if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
+ if (sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) {
list.remove(sa.getHostCard());
@@ -730,7 +730,7 @@ public class PumpAi extends PumpAiBase {
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
if (numDefense.equals("-X") && sa.getSVar("X").equals("Count$ChosenNumber")) {
- int energy = ai.getCounters(CounterType.ENERGY);
+ int energy = ai.getCounters(CounterEnumType.ENERGY);
for (SpellAbility s : source.getSpellAbilities()) {
if ("PayEnergy".equals(s.getParam("AILogic"))) {
energy += AbilityUtils.calculateAmount(source, s.getParam("CounterNum"), sa);
@@ -860,7 +860,7 @@ public class PumpAi extends PumpAiBase {
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
- if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.POISON)) {
+ if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
}
@@ -976,7 +976,7 @@ public class PumpAi extends PumpAiBase {
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
- if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.POISON)) {
+ if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
index eb2ab18e1d1..aba764dbe89 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
@@ -94,7 +94,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
List attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate() {
@Override
public boolean apply(final Card c) {
- if (c.equals(sa.getHostCard()) && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
+ if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
&& (combat == null || !combat.isAttacking(c))) {
return false;
}
@@ -112,7 +112,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
List attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate() {
@Override
public boolean apply(final Card c) {
- if (c.equals(sa.getHostCard()) && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
+ if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
&& (combat == null || !combat.isAttacking(c))) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java b/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java
index e8092eccfa2..4de6ff75187 100644
--- a/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java
@@ -26,7 +26,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
final PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
final Card source = sa.getHostCard();
- if (source.isPermanent() && sa.getRestrictions().isInstantSpeed() && sa.getPayCosts() != null
+ if (source.isPermanent() && sa.getRestrictions().isInstantSpeed()
&& (sa.getPayCosts().hasTapCost() || sa.getPayCosts().hasManaCost())) {
// If it has an associated cost, try to only do this before own turn
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java b/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java
index 8ba1494b666..fcbb622caf3 100644
--- a/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java
@@ -1,6 +1,5 @@
package forge.ai.ability;
-import com.google.common.base.Predicate;
import forge.ai.ComputerUtilCard;
import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
@@ -16,6 +15,7 @@ import forge.game.zone.ZoneType;
import forge.util.TextUtil;
import java.util.List;
+import java.util.Map;
public class RepeatEachAi extends SpellAbilityAi {
@@ -47,21 +47,6 @@ public class RepeatEachAi extends SpellAbilityAi {
return false;
}
}
- } else if ("GainControlOwns".equals(logic)) {
- List list = CardLists.filter(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), new Predicate() {
- @Override
- public boolean apply(final Card crd) {
- return crd.isCreature() && !crd.getController().equals(crd.getOwner());
- }
- });
- if (list.isEmpty()) {
- return false;
- }
- for (final Card c : list) {
- if (aiPlayer.equals(c.getController())) {
- return false;
- }
- }
} else if ("OpponentHasCreatures".equals(logic)) {
for (Player opp : aiPlayer.getOpponents()) {
if (!opp.getCreaturesInPlay().isEmpty()){
@@ -118,7 +103,7 @@ public class RepeatEachAi extends SpellAbilityAi {
}
@Override
- protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) {
+ protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer, Map params) {
return ComputerUtilCard.getBestCreatureAI(options);
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ScryAi.java b/forge-ai/src/main/java/forge/ai/ability/ScryAi.java
index cf01c1d8026..2ec63fb82ac 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ScryAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ScryAi.java
@@ -46,12 +46,10 @@ public class ScryAi extends SpellAbilityAi {
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
// even if there's no mana cost.
- if (sa.getPayCosts() != null) {
- if (sa.getPayCosts().hasTapCost()
- && (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
- && !SpellAbilityAi.isSorcerySpeed(sa)) {
- return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
- }
+ if (sa.getPayCosts().hasTapCost()
+ && (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
+ && !SpellAbilityAi.isSorcerySpeed(sa)) {
+ return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
}
// AI logic to scry in Main 1 if there is no better option, otherwise scry at opponent's EOT
@@ -76,8 +74,7 @@ public class ScryAi extends SpellAbilityAi {
boolean hasSomethingElse = false;
for (Card c : CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.not(CardPredicates.Presets.LANDS))) {
for (SpellAbility ab : c.getAllSpellAbilities()) {
- if (ab.getPayCosts() != null
- && ab.getPayCosts().hasManaCost()
+ if (ab.getPayCosts().hasManaCost()
&& ComputerUtilMana.hasEnoughManaSourcesToCast(ab, ai)) {
// TODO: currently looks for non-Scry cards, can most certainly be made smarter.
if (ab.getApi() != ApiType.Scry) {
@@ -102,7 +99,7 @@ public class ScryAi extends SpellAbilityAi {
} else if ("BrainJar".equals(aiLogic)) {
final Card source = sa.getHostCard();
- int counterNum = source.getCounters(CounterType.CHARGE);
+ int counterNum = source.getCounters(CounterEnumType.CHARGE);
// no need for logic
if (counterNum == 0) {
return false;
diff --git a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
index 7e03c1cf0f5..ca7404dee76 100644
--- a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
@@ -248,7 +248,7 @@ public class SetStateAi extends SpellAbilityAi {
final Card othercard = aiPlayer.getCardsIn(ZoneType.Battlefield, other.getName()).getFirst();
// for legendary KI counter creatures
- if (othercard.getCounters(CounterType.KI) >= source.getCounters(CounterType.KI)) {
+ if (othercard.getCounters(CounterEnumType.KI) >= source.getCounters(CounterEnumType.KI)) {
// if the other legendary is useless try to replace it
return ComputerUtilCard.isUselessCreature(aiPlayer, othercard);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java b/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java
index eb2beab480b..fb5e6efa0f7 100644
--- a/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java
@@ -47,12 +47,10 @@ public class SurveilAi extends SpellAbilityAi {
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
// even if there's no mana cost.
- if (sa.getPayCosts() != null) {
- if (sa.getPayCosts().hasTapCost()
- && (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
- && !SpellAbilityAi.isSorcerySpeed(sa)) {
- return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
- }
+ if (sa.getPayCosts().hasTapCost()
+ && (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
+ && !SpellAbilityAi.isSorcerySpeed(sa)) {
+ return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
}
// in the player's turn Surveil should only be done in Main1 or in Upkeep if able
diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAi.java b/forge-ai/src/main/java/forge/ai/ability/TapAi.java
index a4eb933dbe4..54da422ee87 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TapAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TapAi.java
@@ -3,6 +3,7 @@ package forge.ai.ability;
import forge.ai.*;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
+import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
@@ -68,7 +69,7 @@ public class TapAi extends TapAiBase {
} else {
if ("TapForXCounters".equals(sa.getParam("AILogic"))) {
// e.g. Waxmane Baku
- CounterType ctrType = CounterType.KI;
+ CounterType ctrType = CounterType.get(CounterEnumType.KI);
for (CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {
ctrType = ((CostRemoveCounter)part).counter;
diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java
index 6a408821cbe..34fbe8f5c8b 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java
@@ -126,7 +126,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
}
for (final SpellAbility sa : c.getSpellAbilities()) {
- if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
+ if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}
@@ -147,7 +147,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
}
for (final SpellAbility sa : c.getSpellAbilities()) {
- if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
+ if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
index a5c3a57fad9..f6a62f567ef 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
@@ -1,11 +1,12 @@
package forge.ai.ability;
+import java.util.Map;
+
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.*;
import forge.game.Game;
import forge.game.GameEntity;
-import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
@@ -25,16 +26,9 @@ import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
-import forge.game.trigger.Trigger;
-import forge.game.trigger.TriggerHandler;
import forge.game.zone.ZoneType;
-import forge.item.PaperToken;
-import forge.util.MyRandom;
-import forge.util.TextUtil;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import forge.util.MyRandom;
/**
*
- *
- * a {@link forge.game.ability.AbilityFactory} object.
- */
- private void readParameters(final SpellAbility mapParams) {
- this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1");
-
- this.actualToken = TokenInfo.getProtoType(mapParams.getParam("TokenScript"), mapParams);
-
- if (actualToken == null) {
- this.tokenPower = mapParams.getParam("TokenPower");
- this.tokenToughness = mapParams.getParam("TokenToughness");
- } else {
- this.tokenPower = actualToken.getBasePowerString();
- this.tokenToughness = actualToken.getBaseToughnessString();
- }
- }
-
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
- readParameters(sa); // remember to call this somewhere!
+
final Card source = sa.getHostCard();
// Planeswalker-related flags
boolean pwMinus = false;
@@ -96,21 +65,23 @@ public class TokenAi extends SpellAbilityAi {
}
}
}
+ String tokenAmount = sa.getParamOrDefault("TokenAmount", "1");
- if (actualToken == null) {
- actualToken = spawnToken(ai, sa);
- }
+ Card actualToken = spawnToken(ai, sa);
- if (actualToken == null) {
+ if (actualToken == null || actualToken.getNetToughness() < 1) {
final AbilitySub sub = sa.getSubAbility();
// useful
// no token created
return pwPlus || (sub != null && SpellApiToAi.Converter.get(sub.getApi()).chkAIDrawback(sub, ai)); // planeswalker plus ability or sub-ability is
}
+ String tokenPower = sa.getParamOrDefault("TokenPower", actualToken.getBasePowerString());
+ String tokenToughness = sa.getParamOrDefault("TokenToughness", actualToken.getBaseToughnessString());
+
// X-cost spells
- if (this.tokenAmount.equals("X") || (this.tokenToughness != null && this.tokenToughness.equals("X"))) {
- int x = AbilityUtils.calculateAmount(sa.getHostCard(), this.tokenAmount, sa);
+ if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) {
+ int x = AbilityUtils.calculateAmount(sa.getHostCard(), tokenAmount, sa);
if (source.getSVar("X").equals("Count$Converge")) {
x = ComputerUtilMana.getConvergeCount(sa, ai);
}
@@ -124,14 +95,14 @@ public class TokenAi extends SpellAbilityAi {
}
}
- if (canInterruptSacrifice(ai, sa, actualToken)) {
+ if (canInterruptSacrifice(ai, sa, actualToken, tokenAmount)) {
return true;
}
- boolean haste = this.actualToken.hasKeyword(Keyword.HASTE);
+ boolean haste = actualToken.hasKeyword(Keyword.HASTE);
boolean oneShot = sa.getSubAbility() != null
&& sa.getSubAbility().getApi() == ApiType.DelayedTrigger;
- boolean isCreature = this.actualToken.getType().isCreature();
+ boolean isCreature = actualToken.getType().isCreature();
// Don't generate tokens without haste before main 2 if possible
if (ph.getPhase().isBefore(PhaseType.MAIN2) && ph.isPlayerTurn(ai) && !haste && !sa.hasParam("ActivationPhases")
@@ -166,9 +137,10 @@ public class TokenAi extends SpellAbilityAi {
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; // prevent infinite tokens?
}
+ Card actualToken = spawnToken(ai, sa);
// Don't kill AIs Legendary tokens
- if (this.actualToken.getType().isLegendary() && ai.isCardInPlay(this.actualToken.getName())) {
+ if (actualToken.getType().isLegendary() && ai.isCardInPlay(actualToken.getName())) {
// TODO Check if Token is useless due to an aura or counters?
return false;
}
@@ -240,7 +212,7 @@ public class TokenAi extends SpellAbilityAi {
/**
* Checks if the token(s) can save a creature from a sacrifice effect
*/
- private boolean canInterruptSacrifice(final Player ai, final SpellAbility sa, final Card token) {
+ private boolean canInterruptSacrifice(final Player ai, final SpellAbility sa, final Card token, final String tokenAmount) {
final Game game = ai.getGame();
if (game.getStack().isEmpty()) {
return false; // nothing to interrupt
@@ -249,7 +221,7 @@ public class TokenAi extends SpellAbilityAi {
if (topStack.getApi() != ApiType.Sacrifice) {
return false; // not sacrifice effect
}
- final int nTokens = AbilityUtils.calculateAmount(sa.getHostCard(), this.tokenAmount, sa);
+ final int nTokens = AbilityUtils.calculateAmount(sa.getHostCard(), tokenAmount, sa);
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
String num = sa.getParam("Amount");
num = (num == null) ? "1" : num;
@@ -271,7 +243,8 @@ public class TokenAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
- readParameters(sa);
+ String tokenAmount = sa.getParamOrDefault("TokenAmount", "1");
+
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
@@ -282,8 +255,12 @@ public class TokenAi extends SpellAbilityAi {
sa.getTargets().add(ai);
}
}
- if ("X".equals(this.tokenAmount) || "X".equals(this.tokenPower) || "X".equals(this.tokenToughness)) {
- int x = AbilityUtils.calculateAmount(source, this.tokenAmount, sa);
+ Card actualToken = spawnToken(ai, sa);
+ String tokenPower = sa.getParamOrDefault("TokenPower", actualToken.getBasePowerString());
+ String tokenToughness = sa.getParamOrDefault("TokenToughness", actualToken.getBaseToughnessString());
+
+ if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) {
+ int x = AbilityUtils.calculateAmount(source, tokenAmount, sa);
if (source.getSVar("X").equals("Count$xPaid")) {
// Set PayX here to maximum value.
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
@@ -321,9 +298,7 @@ public class TokenAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, Iterable options)
*/
@Override
- protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options) {
- // TODO: AILogic
- readParameters(sa); // remember to call this somewhere!
+ protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options, Map params) {
Combat combat = ai.getGame().getCombat();
// TokenAttacking
if (combat != null && sa.hasParam("TokenAttacking")) {
@@ -341,9 +316,7 @@ public class TokenAi extends SpellAbilityAi {
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayerOrPlaneswalker(forge.game.player.Player, forge.card.spellability.SpellAbility, Iterable options)
*/
@Override
- protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable options) {
- // TODO: AILogic
- readParameters(sa); // remember to call this somewhere!
+ protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable options, Map params) {
Combat combat = ai.getGame().getCombat();
// TokenAttacking
if (combat != null && sa.hasParam("TokenAttacking")) {
@@ -374,154 +347,22 @@ public class TokenAi extends SpellAbilityAi {
* @param sa Token SpellAbility
* @return token creature created by ability
*/
- @Deprecated
public static Card spawnToken(Player ai, SpellAbility sa) {
- return spawnToken(ai, sa, false);
- }
-
- /**
- * Create the token as a Card object.
- * @param ai owner of the new token
- * @param sa Token SpellAbility
- * @param notNull if the token would not survive, still return it
- * @return token creature created by ability
- */
- // TODO Is this just completely copied from TokenEffect? Let's just call that thing
- @Deprecated
- public static Card spawnToken(Player ai, SpellAbility sa, boolean notNull) {
- final Card host = sa.getHostCard();
-
+ if (!sa.hasParam("TokenScript")) {
+ throw new RuntimeException("Spell Ability has no TokenScript: " + sa);
+ }
Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa);
- if (result != null) {
- result.setController(ai, 0);
- return result;
+ if (result == null) {
+ throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript"));
}
- String[] tokenKeywords = sa.hasParam("TokenKeywords") ? sa.getParam("TokenKeywords").split("<>") : new String[0];
- String tokenPower = sa.getParam("TokenPower");
- String tokenToughness = sa.getParam("TokenToughness");
- String tokenName = sa.getParam("TokenName");
- String[] tokenTypes = sa.getParam("TokenTypes").split(",");
- StringBuilder cost = new StringBuilder();
- String[] tokenColors = sa.getParam("TokenColors").split(",");
- String tokenImage = sa.hasParam("TokenImage") ? PaperToken.makeTokenFileName(sa.getParam("TokenImage")) : "";
- String[] tokenAbilities = sa.hasParam("TokenAbilities") ? sa.getParam("TokenAbilities").split(",") : null;
- String[] tokenTriggers = sa.hasParam("TokenTriggers") ? sa.getParam("TokenTriggers").split(",") : null;
- String[] tokenSVars = sa.hasParam("TokenSVars") ? sa.getParam("TokenSVars").split(",") : null;
- String[] tokenStaticAbilities = sa.hasParam("TokenStaticAbilities") ? sa.getParam("TokenStaticAbilities").split(",") : null;
- String[] tokenHiddenKeywords = sa.hasParam("TokenHiddenKeywords") ? sa.getParam("TokenHiddenKeywords").split("&") : null;
- final String[] substitutedColors = Arrays.copyOf(tokenColors, tokenColors.length);
- for (int i = 0; i < substitutedColors.length; i++) {
- if (substitutedColors[i].equals("ChosenColor")) {
- // this currently only supports 1 chosen color
- substitutedColors[i] = host.getChosenColor();
- }
- }
- StringBuilder colorDesc = new StringBuilder();
- for (final String col : substitutedColors) {
- if (col.equalsIgnoreCase("White")) {
- colorDesc.append("W ");
- } else if (col.equalsIgnoreCase("Blue")) {
- colorDesc.append("U ");
- } else if (col.equalsIgnoreCase("Black")) {
- colorDesc.append("B ");
- } else if (col.equalsIgnoreCase("Red")) {
- colorDesc.append("R ");
- } else if (col.equalsIgnoreCase("Green")) {
- colorDesc.append("G ");
- } else if (col.equalsIgnoreCase("Colorless")) {
- colorDesc = new StringBuilder("C");
- }
- }
-
- final List imageNames = new ArrayList<>(1);
- if (tokenImage.equals("")) {
- imageNames.add(PaperToken.makeTokenFileName(TextUtil.fastReplace(colorDesc.toString(), " ", ""), tokenPower, tokenToughness, tokenName));
- } else {
- imageNames.add(0, tokenImage);
- }
+ result.setOwner(ai);
- for (final char c : colorDesc.toString().toCharArray()) {
- cost.append(c + ' ');
- }
-
- cost = new StringBuilder(colorDesc.toString().replace('C', '1').trim());
-
- final int finalPower = AbilityUtils.calculateAmount(host, tokenPower, sa);
- final int finalToughness = AbilityUtils.calculateAmount(host, tokenToughness, sa);
-
- final String[] substitutedTypes = Arrays.copyOf(tokenTypes, tokenTypes.length);
- for (int i = 0; i < substitutedTypes.length; i++) {
- if (substitutedTypes[i].equals("ChosenType")) {
- substitutedTypes[i] = host.getChosenType();
- }
- }
- final String substitutedName = tokenName.equals("ChosenType") ? host.getChosenType() : tokenName;
- final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size()));
- final TokenInfo tokenInfo = new TokenInfo(substitutedName, imageName,
- cost.toString(), substitutedTypes, tokenKeywords, finalPower, finalToughness);
- Card token = tokenInfo.makeOneToken(ai);
-
- if (token == null) {
- return null;
- }
-
- // Grant rule changes
- if (tokenHiddenKeywords != null) {
- for (final String s : tokenHiddenKeywords) {
- token.addHiddenExtrinsicKeyword(s);
- }
- }
-
- // Grant abilities
- if (tokenAbilities != null) {
- for (final String s : tokenAbilities) {
- final String actualAbility = host.getSVar(s);
- final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, token);
- token.addSpellAbility(grantedAbility);
- }
- }
-
- // Grant triggers
- if (tokenTriggers != null) {
- for (final String s : tokenTriggers) {
- final String actualTrigger = host.getSVar(s);
- final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, token, true);
- final String ability = host.getSVar(parsedTrigger.getParam("Execute"));
- parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, token));
- token.addTrigger(parsedTrigger);
- }
- }
-
- // Grant SVars
- if (tokenSVars != null) {
- for (final String s : tokenSVars) {
- String actualSVar = host.getSVar(s);
- String name = s;
- if (actualSVar.startsWith("SVar")) {
- actualSVar = actualSVar.split("SVar:")[1];
- name = actualSVar.split(":")[0];
- actualSVar = actualSVar.split(":")[1];
- }
- token.setSVar(name, actualSVar);
- }
- }
-
- // Grant static abilities
- if (tokenStaticAbilities != null) {
- for (final String s : tokenStaticAbilities) {
- token.addStaticAbility(host.getSVar(s));
- }
- }
-
- // Apply static abilities and prune dead tokens
+ // Apply static abilities
final Game game = ai.getGame();
- ComputerUtilCard.applyStaticContPT(game, token, null);
- if (!notNull && token.isCreature() && token.getNetToughness() < 1) {
- return null;
- } else {
- return token;
- }
+ ComputerUtilCard.applyStaticContPT(game, result, null);
+ return result;
}
+
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java
index 5e2659fdacc..be7eb4f2ae9 100644
--- a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java
@@ -24,6 +24,7 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import java.util.List;
+import java.util.Map;
public class UntapAi extends SpellAbilityAi {
@Override
@@ -153,12 +154,11 @@ public class UntapAi extends SpellAbilityAi {
// Try to avoid potential infinite recursion,
// e.g. Kiora's Follower untapping another Kiora's Follower and repeating infinitely
- if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostTap.class)) {
+ if (sa.getPayCosts().hasOnlySpecificCostType(CostTap.class)) {
CardCollection toRemove = new CardCollection();
for (Card c : untapList) {
for (SpellAbility ab : c.getAllSpellAbilities()) {
if (ab.getApi() == ApiType.Untap
- && ab.getPayCosts() != null
&& ab.getPayCosts().hasOnlySpecificCostType(CostTap.class)
&& ab.canTarget(source)) {
toRemove.add(c);
@@ -312,7 +312,7 @@ public class UntapAi extends SpellAbilityAi {
}
@Override
- public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable list, boolean isOptional, Player targetedPlayer) {
+ public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable list, boolean isOptional, Player targetedPlayer, Map params) {
PlayerCollection pl = new PlayerCollection();
pl.add(ai);
pl.addAll(ai.getAllies());
diff --git a/forge-ai/src/main/java/forge/ai/ability/VoteAi.java b/forge-ai/src/main/java/forge/ai/ability/VoteAi.java
index 58aab03ecbb..33d23dd3b22 100644
--- a/forge-ai/src/main/java/forge/ai/ability/VoteAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/VoteAi.java
@@ -46,6 +46,12 @@ public class VoteAi extends SpellAbilityAi {
@Override
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map params) {
+ if (params.containsKey("Voter")) {
+ Player p = (Player)params.get("Voter");
+ if (p.isOpponentOf(player)) {
+ return min;
+ }
+ }
if (sa.getActivatingPlayer().isOpponentOf(player)) {
return min;
}
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 cd99a5a6d94..1fac17f367a 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
@@ -76,7 +76,7 @@ public class GameCopier {
newPlayer.addSpellCastThisTurn();
for (int j = 0; j < origPlayer.getLandsPlayedThisTurn(); j++)
newPlayer.addLandPlayedThisTurn();
- newPlayer.setCounters(Maps.newEnumMap(origPlayer.getCounters()));
+ newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
newPlayer.setPreventNextDamage(origPlayer.getPreventNextDamage());
@@ -125,8 +125,6 @@ public class GameCopier {
}
}
}
- origGame.validateSpabCache();
- newGame.validateSpabCache();
// Undo effects first before calculating them below, to avoid them applying twice.
for (StaticEffect effect : origGame.getStaticEffects().getEffects()) {
@@ -330,7 +328,7 @@ public class GameCopier {
Map counters = c.getCounters();
if (!counters.isEmpty()) {
- newCard.setCounters(Maps.newEnumMap(counters));
+ newCard.setCounters(Maps.newHashMap(counters));
}
if (c.getChosenPlayer() != null) {
newCard.setChosenPlayer(playerMap.get(c.getChosenPlayer()));
diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java
index f2d0f01bf85..e3dc9e8a64c 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java
@@ -3,7 +3,7 @@ package forge.ai.simulation;
import forge.ai.CreatureEvaluator;
import forge.game.Game;
import forge.game.card.Card;
-import forge.game.card.CounterType;
+import forge.game.card.CounterEnumType;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
@@ -154,7 +154,7 @@ public class GameStateEvaluator {
// e.g. a 5 CMC permanent results in 200, whereas a 5/5 creature is ~225
int value = 50 + 30 * c.getCMC();
if (c.isPlaneswalker()) {
- value += 2 * c.getCounters(CounterType.LOYALTY);
+ value += 2 * c.getCounters(CounterEnumType.LOYALTY);
}
return value;
}
diff --git a/forge-core/pom.xml b/forge-core/pom.xml
index 9c13b0d69aa..620b62074e2 100644
--- a/forge-core/pom.xml
+++ b/forge-core/pom.xml
@@ -6,7 +6,7 @@
forgeforge
- 1.6.33-SNAPSHOT
+ 1.6.36-SNAPSHOTforge-core
diff --git a/forge-core/src/main/java/forge/ImageKeys.java b/forge-core/src/main/java/forge/ImageKeys.java
index e07f836566d..4ea30d2a17d 100644
--- a/forge-core/src/main/java/forge/ImageKeys.java
+++ b/forge-core/src/main/java/forge/ImageKeys.java
@@ -6,6 +6,8 @@ import forge.util.ImageUtil;
import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
+
+
import java.io.File;
import java.util.HashMap;
import java.util.Map;
@@ -113,7 +115,17 @@ public final class ImageKeys {
}
//try fullborder...
if (filename.contains(".full")) {
- file = findFile(dir, TextUtil.fastReplace(filename, ".full", ".fullborder"));
+ String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder");
+ file = findFile(dir, fullborderFile);
+ if (file != null) { return file; }
+ // if there's a 1st art variant try without it for .fullborder images
+ file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
+ if (file != null) { return file; }
+ // if there's an art variant try without it for .full images
+ file = findFile(dir, filename.replaceAll("[0-9].full]",".full"));
+ if (file != null) { return file; }
+ // if there's a 1st art variant try with it for .full images
+ file = findFile(dir, filename.replaceAll("[0-9]*.full", "1.full"));
if (file != null) { return file; }
}
//if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder
diff --git a/forge-core/src/main/java/forge/StaticData.java b/forge-core/src/main/java/forge/StaticData.java
index 6b65424daab..af533330a5b 100644
--- a/forge-core/src/main/java/forge/StaticData.java
+++ b/forge-core/src/main/java/forge/StaticData.java
@@ -54,11 +54,11 @@ public class StaticData {
private static StaticData lastInstance = null;
- public StaticData(CardStorageReader cardReader, String editionFolder, String blockDataFolder) {
- this(cardReader, null, editionFolder, blockDataFolder);
+ public StaticData(CardStorageReader cardReader, String editionFolder, String blockDataFolder, boolean enableUnknownCards) {
+ this(cardReader, null, editionFolder, blockDataFolder, enableUnknownCards);
}
- public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, String editionFolder, String blockDataFolder) {
+ public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, String editionFolder, String blockDataFolder, boolean enableUnknownCards) {
this.cardReader = cardReader;
this.tokenReader = tokenReader;
this.editions = new CardEdition.Collection(new CardEdition.Reader(new File(editionFolder)));
@@ -84,8 +84,8 @@ public class StaticData {
variantCards = new CardDb(variantsCards, editions);
//must initialize after establish field values for the sake of card image logic
- commonCards.initialize(false, false);
- variantCards.initialize(false, false);
+ commonCards.initialize(false, false, enableUnknownCards);
+ variantCards.initialize(false, false, enableUnknownCards);
}
{
@@ -215,7 +215,7 @@ public class StaticData {
public Predicate getStandardPredicate() { return standardPredicate; }
public Predicate getPioneerPredicate() { return pioneerPredicate; }
-
+
public Predicate getModernPredicate() { return modernPredicate; }
public Predicate getCommanderPredicate() { return commanderPredicate; }
diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java
index 2c4e54ad21a..a7b10ec600e 100644
--- a/forge-core/src/main/java/forge/card/CardDb.java
+++ b/forge-core/src/main/java/forge/card/CardDb.java
@@ -165,7 +165,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
reIndex();
}
- public void initialize(boolean logMissingPerEdition, boolean logMissingSummary) {
+ public void initialize(boolean logMissingPerEdition, boolean logMissingSummary, boolean enableUnknownCards) {
Set allMissingCards = new LinkedHashSet<>();
List missingCards = new ArrayList<>();
CardEdition upcomingSet = null;
@@ -218,7 +218,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
if (!contains(cr.getName())) {
if (upcomingSet != null) {
addCard(new PaperCard(cr, upcomingSet.getCode(), CardRarity.Unknown, 1));
- } else {
+ } else if(enableUnknownCards) {
System.err.println("The card " + cr.getName() + " was not assigned to any set. Adding it to UNKNOWN set... to fix see res/editions/ folder. ");
addCard(new PaperCard(cr, CardEdition.UNKNOWN.getCode(), CardRarity.Special, 1));
}
@@ -312,17 +312,21 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return tryGetCard(request);
}
- public int getCardCollectorNumber(String cardName, String reqEdition) {
+ public String getCardCollectorNumber(String cardName, String reqEdition, int artIndex) {
cardName = getName(cardName);
CardEdition edition = editions.get(reqEdition);
if (edition == null)
- return -1;
+ return null;
+ int numMatches = 0;
for (CardInSet card : edition.getCards()) {
if (card.name.equalsIgnoreCase(cardName)) {
- return card.collectorNumber;
+ numMatches += 1;
+ if (numMatches == artIndex) {
+ return card.collectorNumber;
+ }
}
}
- return -1;
+ return null;
}
private PaperCard tryGetCard(CardRequest request) {
diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java
index e2b786c0322..ce6289bb5f5 100644
--- a/forge-core/src/main/java/forge/card/CardEdition.java
+++ b/forge-core/src/main/java/forge/card/CardEdition.java
@@ -38,6 +38,8 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
@@ -75,10 +77,10 @@ public final class CardEdition implements Comparable { // immutable
public static class CardInSet {
public final CardRarity rarity;
- public final int collectorNumber;
+ public final String collectorNumber;
public final String name;
- public CardInSet(final String name, final int collectorNumber, final CardRarity rarity) {
+ public CardInSet(final String name, final String collectorNumber, final CardRarity rarity) {
this.name = name;
this.collectorNumber = collectorNumber;
this.rarity = rarity;
@@ -86,7 +88,7 @@ public final class CardEdition implements Comparable { // immutable
public String toString() {
StringBuilder sb = new StringBuilder();
- if (collectorNumber != -1) {
+ if (collectorNumber != null) {
sb.append(collectorNumber);
sb.append(' ');
}
@@ -190,6 +192,7 @@ public final class CardEdition implements Comparable { // immutable
public boolean getSmallSetOverride() { return smallSetOverride; }
public String getBoosterMustContain() { return boosterMustContain; }
public CardInSet[] getCards() { return cards; }
+ public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
public Map getTokens() { return tokenNormalized; }
@@ -266,24 +269,33 @@ public final class CardEdition implements Comparable { // immutable
Map tokenNormalized = new HashMap<>();
List processedCards = new ArrayList<>();
if (contents.containsKey("cards")) {
+ final Pattern pattern = Pattern.compile(
+ /*
+ The following pattern will match the WAR Japanese art entries,
+ it should also match the Un-set and older alternate art cards
+ like Merseine from FEM (should the editions files ever be updated)
+ */
+ //"(^(?[0-9]+.?) )?((?[SCURML]) )?(?.*)$"
+ /* Ideally we'd use the named group above, but Android 6 and
+ earlier don't appear to support named groups.
+ So, untill support for those devices is officially dropped,
+ we'll have to suffice with numbered groups.
+ We are looking for:
+ * cnum - grouping #2
+ * rarity - grouping #4
+ * name - grouping #5
+ */
+ "(^([0-9]+.?) )?(([SCURML]) )?(.*)$"
+ );
for(String line : contents.get("cards")) {
- if (StringUtils.isBlank(line))
- continue;
-
- // Optional collector number at the start.
- String[] split = line.split(" ", 2);
- int collectorNumber = -1;
- if (split.length >= 2 && StringUtils.isNumeric(split[0])) {
- collectorNumber = Integer.parseInt(split[0]);
- line = split[1];
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.matches()) {
+ String collectorNumber = matcher.group(2);
+ CardRarity r = CardRarity.smartValueOf(matcher.group(4));
+ String cardName = matcher.group(5);
+ CardInSet cis = new CardInSet(cardName, collectorNumber, r);
+ processedCards.add(cis);
}
-
- // You may omit rarity for early development
- CardRarity r = CardRarity.smartValueOf(line.substring(0, 1));
- boolean hadRarity = r != CardRarity.Unknown && line.charAt(1) == ' ';
- String cardName = hadRarity ? line.substring(2) : line;
- CardInSet cis = new CardInSet(cardName, collectorNumber, r);
- processedCards.add(cis);
}
}
diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java
index 322c003b6f2..6cd5b4daaee 100644
--- a/forge-core/src/main/java/forge/card/CardRules.java
+++ b/forge-core/src/main/java/forge/card/CardRules.java
@@ -222,7 +222,12 @@ public final class CardRules implements ICardCharacteristics {
public boolean canBeBrawlCommander() {
CardType type = mainPart.getType();
- return (type.isLegendary() && type.isCreature()) || type.isPlaneswalker();
+ return type.isLegendary() && (type.isCreature() || type.isPlaneswalker());
+ }
+
+ public boolean canBeTinyLeadersCommander() {
+ CardType type = mainPart.getType();
+ return type.isLegendary() && (type.isCreature() || type.isPlaneswalker());
}
public String getMeldWith() {
@@ -526,12 +531,10 @@ public final class CardRules implements ICardCharacteristics {
public final ManaCostShard next() {
final String unparsed = st.nextToken();
// System.out.println(unparsed);
- try {
- int iVal = Integer.parseInt(unparsed);
- this.genericCost += iVal;
+ if (StringUtils.isNumeric(unparsed)) {
+ this.genericCost += Integer.parseInt(unparsed);
return null;
}
- catch (NumberFormatException nex) { }
return ManaCostShard.parseNonGeneric(unparsed);
}
diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java
index 6679b3ad673..66dca47b592 100644
--- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java
+++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java
@@ -594,8 +594,10 @@ public final class CardRulesPredicates {
public static final Predicate IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard);
public static final Predicate IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
public static final Predicate IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
- public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER,
- Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY));
+ public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
+ Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
+ public static final Predicate CAN_BE_TINY_LEADERS_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
+ Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
/** The Constant IS_NON_CREATURE_SPELL. **/
public static final Predicate IS_NON_CREATURE_SPELL = com.google.common.base.Predicates
diff --git a/forge-core/src/main/java/forge/card/CardType.java b/forge-core/src/main/java/forge/card/CardType.java
index 4a302735d76..09d6abbd2f9 100644
--- a/forge-core/src/main/java/forge/card/CardType.java
+++ b/forge-core/src/main/java/forge/card/CardType.java
@@ -72,6 +72,14 @@ public final class CardType implements Comparable, CardTypeView {
private static Map stringToCoreType = EnumUtils.getEnumMap(CoreType.class);
private static final Set allCoreTypeNames = stringToCoreType.keySet();
+ public static CoreType getEnum(String name) {
+ return stringToCoreType.get(name);
+ }
+
+ public static boolean isValidEnum(String name) {
+ return stringToCoreType.containsKey(name);
+ }
+
CoreType(final boolean permanent) {
isPermanent = permanent;
}
@@ -87,6 +95,15 @@ public final class CardType implements Comparable, CardTypeView {
private static Map stringToSupertype = EnumUtils.getEnumMap(Supertype.class);
private static final Set allSuperTypeNames = stringToSupertype.keySet();
+
+ public static Supertype getEnum(String name) {
+ return stringToSupertype.get(name);
+ }
+
+ public static boolean isValidEnum(String name) {
+ return stringToSupertype.containsKey(name);
+ }
+
}
private final Set coreTypes = EnumSet.noneOf(CoreType.class);
@@ -108,12 +125,12 @@ public final class CardType implements Comparable, CardTypeView {
public boolean add(final String t) {
boolean changed;
- final CoreType ct = EnumUtils.getEnum(CoreType.class, t);
+ final CoreType ct = CoreType.getEnum(t);
if (ct != null) {
changed = coreTypes.add(ct);
}
else {
- final Supertype st = EnumUtils.getEnum(Supertype.class, t);
+ final Supertype st = Supertype.getEnum(t);
if (st != null) {
changed = supertypes.add(st);
}
@@ -183,11 +200,11 @@ public final class CardType implements Comparable, CardTypeView {
if (subtypes.remove(str)) {
changed = true;
} else {
- Supertype st = EnumUtils.getEnum(Supertype.class, str);
+ Supertype st = Supertype.getEnum(str);
if (st != null && supertypes.remove(st)) {
changed = true;
}
- CoreType ct = EnumUtils.getEnum(CoreType.class, str);
+ CoreType ct = CoreType.getEnum(str);
if (ct != null && coreTypes.remove(ct)) {
changed = true;
}
@@ -265,11 +282,11 @@ public final class CardType implements Comparable, CardTypeView {
}
t = StringUtils.capitalize(t);
- final CoreType type = EnumUtils.getEnum(CoreType.class, t);
+ final CoreType type = CoreType.getEnum(t);
if (type != null) {
return hasType(type);
}
- final Supertype supertype = EnumUtils.getEnum(Supertype.class, t);
+ final Supertype supertype = Supertype.getEnum(t);
if (supertype != null) {
return hasSupertype(supertype);
}
@@ -660,7 +677,7 @@ public final class CardType implements Comparable, CardTypeView {
///////// Utility methods
public static boolean isACardType(final String cardType) {
- return EnumUtils.isValidEnum(CoreType.class, cardType);
+ return CoreType.isValidEnum(cardType);
}
public static Set getAllCardTypes() {
@@ -708,7 +725,7 @@ public final class CardType implements Comparable, CardTypeView {
}
public static boolean isASupertype(final String cardType) {
- return EnumUtils.isValidEnum(Supertype.class, cardType);
+ return Supertype.isValidEnum(cardType);
}
public static boolean isASubType(final String cardType) {
diff --git a/forge-core/src/main/java/forge/card/mana/ManaCostShard.java b/forge-core/src/main/java/forge/card/mana/ManaCostShard.java
index 8c97c99e5b8..d4533f1abc5 100644
--- a/forge-core/src/main/java/forge/card/mana/ManaCostShard.java
+++ b/forge-core/src/main/java/forge/card/mana/ManaCostShard.java
@@ -289,13 +289,20 @@ public enum ManaCostShard {
return BinaryUtil.bitCount(this.shard & COLORS_SUPERPOSITION) == 2;
}
+ public boolean isGeneric() {
+ return isOfKind(ManaAtom.GENERIC)|| isOfKind(ManaAtom.IS_X) || this.isSnow() || this.isOr2Generic();
+ }
public boolean isOr2Generic() {
return isOfKind(ManaAtom.OR_2_GENERIC);
}
+
+ public boolean isColor(byte colorCode) {
+ return (colorCode & this.shard) > 0;
+ }
public boolean canBePaidWithManaOfColor(byte colorCode) {
return this.isOr2Generic() || ((COLORS_SUPERPOSITION | ManaAtom.COLORLESS) & this.shard) == 0 ||
- (colorCode & this.shard) > 0;
+ this.isColor(colorCode);
}
public boolean isOfKind(int atom) {
diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java
index 3ea356c6a6d..e369fccc075 100644
--- a/forge-core/src/main/java/forge/deck/DeckFormat.java
+++ b/forge-core/src/main/java/forge/deck/DeckFormat.java
@@ -463,6 +463,9 @@ public enum DeckFormat {
if (this.equals(DeckFormat.Brawl)) {
return rules.canBeBrawlCommander();
}
+ if (this.equals(DeckFormat.TinyLeaders)) {
+ return rules.canBeTinyLeadersCommander();
+ }
return rules.canBeCommander();
}
diff --git a/forge-core/src/main/java/forge/item/IPaperCard.java b/forge-core/src/main/java/forge/item/IPaperCard.java
index 0c14064e059..53ea1d066e4 100644
--- a/forge-core/src/main/java/forge/item/IPaperCard.java
+++ b/forge-core/src/main/java/forge/item/IPaperCard.java
@@ -6,6 +6,7 @@ import forge.card.CardRarity;
import forge.card.CardRules;
import forge.card.CardType.CoreType;
import forge.card.MagicColor;
+import forge.util.PredicateCard;
import forge.util.PredicateString;
import org.apache.commons.lang3.StringUtils;
@@ -60,6 +61,8 @@ public interface IPaperCard extends InventoryItem {
return new PredicateNames(what);
}
+ public static PredicateCards cards(final List what) { return new PredicateCards(what); }
+
private static final class PredicateColor implements Predicate {
private final byte operand;
@@ -161,6 +164,25 @@ public interface IPaperCard extends InventoryItem {
}
}
+ private static final class PredicateCards extends PredicateCard {
+ private final List operand;
+
+ @Override
+ public boolean apply(final PaperCard card) {
+ for (final PaperCard element : this.operand) {
+ if (this.op(card, element)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private PredicateCards(final List operand) {
+ super(StringOp.EQUALS);
+ this.operand = operand;
+ }
+ }
+
/**
* Pre-built predicates are stored here to allow their re-usage and
* easier access from code.
diff --git a/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java b/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java
index 896e8d6de6b..833ee7e6bf7 100644
--- a/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java
+++ b/forge-core/src/main/java/forge/item/generation/BoosterGenerator.java
@@ -566,12 +566,8 @@ public class BoosterGenerator {
toAdd = IPaperCard.Predicates.printedInSets(sets);
} else if (operator.startsWith("fromSheet(") && invert) {
String sheetName = StringUtils.strip(operator.substring(9), "()\" ");
- Iterable src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
- List cardNames = Lists.newArrayList();
- for (PaperCard card : src) {
- cardNames.add(card.getName());
- }
- toAdd = IPaperCard.Predicates.names(Lists.newArrayList(cardNames));
+ Iterable cards = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
+ toAdd = IPaperCard.Predicates.cards(Lists.newArrayList(cards));
}
if (toAdd == null) {
diff --git a/forge-core/src/main/java/forge/util/PredicateCard.java b/forge-core/src/main/java/forge/util/PredicateCard.java
new file mode 100644
index 00000000000..308b34f68ca
--- /dev/null
+++ b/forge-core/src/main/java/forge/util/PredicateCard.java
@@ -0,0 +1,83 @@
+/*
+ * Forge: Play Magic: the Gathering.
+ * Copyright (C) 2020 Jamin W. Collins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package forge.util;
+
+import com.google.common.base.Predicate;
+import forge.item.PaperCard;
+
+/**
+ * Special predicate class to perform string operations.
+ *
+ * @param
+ * the generic type
+ */
+public abstract class PredicateCard implements Predicate {
+ /** Possible operators for string operands. */
+ public enum StringOp {
+ /** The EQUALS. */
+ EQUALS,
+ }
+
+ /** The operator. */
+ private final StringOp operator;
+
+ /**
+ * Op.
+ *
+ * @param op1
+ * the op1
+ * @param op2
+ * the op2
+ * @return true, if successful
+ */
+ protected final boolean op(final PaperCard op1, final PaperCard op2) {
+ switch (this.getOperator()) {
+ case EQUALS:
+ return op1.equals(op2);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Instantiates a new predicate string.
+ *
+ * @param operator
+ * the operator
+ */
+ public PredicateCard(final StringOp operator) {
+ this.operator = operator;
+ }
+
+ /**
+ * @return the operator
+ */
+ public StringOp getOperator() {
+ return operator;
+ }
+
+ public static PredicateCard equals(final PaperCard what) {
+ return new PredicateCard(StringOp.EQUALS) {
+ @Override
+ public boolean apply(PaperCard subject) {
+ return op(subject, what);
+ }
+ };
+ }
+
+}
diff --git a/forge-core/src/main/java/forge/util/TextUtil.java b/forge-core/src/main/java/forge/util/TextUtil.java
index 4974a8e087f..06a706b20f0 100644
--- a/forge-core/src/main/java/forge/util/TextUtil.java
+++ b/forge-core/src/main/java/forge/util/TextUtil.java
@@ -5,6 +5,8 @@ import forge.item.PaperCard;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
+import com.google.common.collect.ImmutableSortedMap;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -17,6 +19,22 @@ import java.util.Map.Entry;
*/
public class TextUtil {
+ static ImmutableSortedMap romanMap = ImmutableSortedMap.naturalOrder()
+ .put(1000, "M").put(900, "CM")
+ .put(500, "D").put(400, "CD")
+ .put(100, "C").put(90, "XC")
+ .put(50, "L").put(40, "XL")
+ .put(10, "X").put(9, "IX")
+ .put(5, "V").put(4, "IV").put(1, "I").build();
+
+ public final static String toRoman(int number) {
+ if (number <= 0) {
+ return "";
+ }
+ int l = romanMap.floorKey(number);
+ return romanMap.get(l) + toRoman(number-l);
+ }
+
/**
* Safely converts an object to a String.
*
diff --git a/forge-game/pom.xml b/forge-game/pom.xml
index 117c4abbebd..e5b60949db6 100644
--- a/forge-game/pom.xml
+++ b/forge-game/pom.xml
@@ -6,7 +6,7 @@
forgeforge
- 1.6.33-SNAPSHOT
+ 1.6.36-SNAPSHOTforge-game
diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java
index 1df09691ee1..1b6da08e1b8 100644
--- a/forge-game/src/main/java/forge/game/CardTraitBase.java
+++ b/forge-game/src/main/java/forge/game/CardTraitBase.java
@@ -5,7 +5,6 @@ import forge.card.mana.ManaAtom;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
-import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardUtil;
import forge.game.card.CardView;
@@ -243,7 +242,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
final String type = params.get("Presence");
int revealed = AbilityUtils.calculateAmount(hostCard, "Revealed$Valid " + type, hostCard.getCastSA());
- int ctrl = AbilityUtils.calculateAmount(hostCard, "Count$Valid " + type + ".inZoneBattlefield+YouCtrl", hostCard.getCastSA());
+ int ctrl = AbilityUtils.calculateAmount(hostCard, "Count$LastStateBattlefield " + type + ".YouCtrl", hostCard.getCastSA());
if (revealed + ctrl == 0) {
return false;
@@ -271,13 +270,8 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
lifeCompare = params.get("LifeAmount");
}
- int right = 1;
final String rightString = lifeCompare.substring(2);
- try {
- right = Integer.parseInt(rightString);
- } catch (final NumberFormatException nfe) {
- right = CardFactoryUtil.xCount(this.getHostCard(), this.getHostCard().getSVar(rightString));
- }
+ int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
if (!Expressions.compare(life, lifeCompare, right)) {
return false;
@@ -314,13 +308,9 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
}
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), null);
- int right = 1;
+
final String rightString = presentCompare.substring(2);
- try {
- right = Integer.parseInt(rightString);
- } catch (final NumberFormatException nfe) {
- right = CardFactoryUtil.xCount(this.getHostCard(), this.getHostCard().getSVar(rightString));
- }
+ int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
final int left = list.size();
if (!Expressions.compare(left, presentCompare, right)) {
@@ -355,13 +345,8 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), null);
- int right = 1;
final String rightString = presentCompare.substring(2);
- try {
- right = Integer.parseInt(rightString);
- } catch (final NumberFormatException nfe) {
- right = CardFactoryUtil.xCount(this.getHostCard(), this.getHostCard().getSVar(rightString));
- }
+ int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
final int left = list.size();
if (!Expressions.compare(left, presentCompare, right)) {
diff --git a/forge-game/src/main/java/forge/game/EvenOdd.java b/forge-game/src/main/java/forge/game/EvenOdd.java
new file mode 100644
index 00000000000..89f759d0109
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/EvenOdd.java
@@ -0,0 +1,6 @@
+package forge.game;
+
+public enum EvenOdd {
+ Even,
+ Odd
+}
diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java
index f21d1155fbd..06a3d41adee 100644
--- a/forge-game/src/main/java/forge/game/ForgeScript.java
+++ b/forge-game/src/main/java/forge/game/ForgeScript.java
@@ -81,7 +81,7 @@ public class ForgeScript {
return !cardState.getTypeWithChanges().hasSubtype(subType);
} else if (property.equals("hasActivatedAbilityWithTapCost")) {
for (final SpellAbility sa : cardState.getSpellAbilities()) {
- if (sa.isAbility() && (sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
+ if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
return true;
}
}
@@ -108,14 +108,9 @@ public class ForgeScript {
}
return false;
} else if (property.startsWith("cmc")) {
- int x;
String rhs = property.substring(5);
int y = cardState.getManaCost().getCMC();
- try {
- x = Integer.parseInt(rhs);
- } catch (final NumberFormatException e) {
- x = AbilityUtils.calculateAmount(source, rhs, spellAbility);
- }
+ int x = AbilityUtils.calculateAmount(source, rhs, spellAbility);
return Expressions.compare(y, property, x);
} else return cardState.getTypeWithChanges().hasStringType(property);
@@ -130,6 +125,8 @@ public class ForgeScript {
return sa.isManaAbility();
} else if (property.equals("nonManaAbility")) {
return !sa.isManaAbility();
+ } else if (property.equals("withoutXCost")) {
+ return !sa.isXCost();
} else if (property.equals("Buyback")) {
return sa.isBuyBackAbility();
} else if (property.equals("Cycling")) {
diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java
index 849e2de4a0d..47997c1b8bc 100644
--- a/forge-game/src/main/java/forge/game/Game.java
+++ b/forge-game/src/main/java/forge/game/Game.java
@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
@@ -40,7 +40,6 @@ import forge.game.player.*;
import forge.game.replacement.ReplacementHandler;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
-import forge.game.spellability.SpellAbilityView;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
import forge.game.zone.CostPaymentStack;
@@ -80,7 +79,7 @@ public class Game {
private final GameLog gameLog = new GameLog();
private final Zone stackZone = new Zone(ZoneType.Stack, this);
-
+
private CardCollection lastStateBattlefield = new CardCollection();
private CardCollection lastStateGraveyard = new CardCollection();
@@ -98,7 +97,7 @@ public class Game {
private GameStage age = GameStage.BeforeMulligan;
private GameOutcome outcome;
- private final GameView view;
+ private final GameView view;
private final Tracker tracker = new Tracker();
public Player getMonarch() {
@@ -178,19 +177,6 @@ public class Game {
playerCache.put(Integer.valueOf(id), player);
}
- private final GameEntityCache cardCache = new GameEntityCache<>();
- public Card getCard(CardView cardView) {
- return cardCache.get(cardView);
- }
- public void addCard(int id, Card card) {
- cardCache.put(Integer.valueOf(id), card);
- }
- public CardCollection getCardList(Iterable cardViews) {
- CardCollection list = new CardCollection();
- cardCache.addToList(cardViews, list);
- return list;
- }
-
// methods that deal with saving, retrieving and clearing LKI information about cards on zone change
private final HashMap changeZoneLKIInfo = new HashMap<>();
public final void addChangeZoneLKIInfo(Card c) {
@@ -209,27 +195,6 @@ public class Game {
changeZoneLKIInfo.clear();
}
- private final GameEntityCache spabCache = new GameEntityCache<>();
- public SpellAbility getSpellAbility(final SpellAbilityView view) {
- return spabCache.get(view);
- }
- public void addSpellAbility(SpellAbility spellAbility) {
- spabCache.put(spellAbility.getId(), spellAbility);
- }
- public void removeSpellAbility(SpellAbility spellAbility) {
- spabCache.remove(spellAbility.getId());
- }
- public void validateSpabCache() {
- for (SpellAbility sa : spabCache.getValues()) {
- if (sa.getHostCard() != null && sa.getHostCard().getGame() != this) {
- throw new RuntimeException();
- }
- if (sa.getActivatingPlayer() != null && sa.getActivatingPlayer().getGame() != this) {
- throw new RuntimeException();
- }
- }
- }
-
public Game(List players0, GameRules rules0, Match match0) { /* no more zones to map here */
rules = rules0;
match = match0;
@@ -407,7 +372,7 @@ public class Game {
}
});
}
-
+
/**
* The Direction in which the turn order of this Game currently proceeds.
*/
@@ -559,6 +524,48 @@ public class Game {
return visit.getFound(notFound);
}
+ private static class CardIdVisitor extends Visitor {
+ Card found = null;
+ int id;
+
+ private CardIdVisitor(final int id) {
+ this.id = id;
+ }
+
+ @Override
+ public boolean visit(Card object) {
+ if (this.id == object.getId()) {
+ found = object;
+ }
+ return found == null;
+ }
+
+ public Card getFound() {
+ return found;
+ }
+ }
+
+ public Card findByView(CardView view) {
+ if (view == null) {
+ return null;
+ }
+ CardIdVisitor visit = new CardIdVisitor(view.getId());
+ if (ZoneType.Stack.equals(view.getZone())) {
+ visit.visitAll(getStackZone());
+ } else if (view.getController() != null && view.getZone() != null) {
+ visit.visitAll(getPlayer(view.getController()).getZone(view.getZone()));
+ } else { // fallback if view doesn't has controller or zone set for some reason
+ forEachCardInGame(visit);
+ }
+ return visit.getFound();
+ }
+
+ public Card findById(int id) {
+ CardIdVisitor visit = new CardIdVisitor(id);
+ this.forEachCardInGame(visit);
+ return visit.getFound();
+ }
+
// Allows visiting cards in game without allocating a temporary list.
public void forEachCardInGame(Visitor visitor) {
for (final Player player : getPlayers()) {
@@ -672,21 +679,25 @@ public class Game {
// Rule 800.4 Losing a Multiplayer game
CardCollectionView cards = this.getCardsInGame();
boolean planarControllerLost = false;
+ boolean isMultiplayer = this.getPlayers().size() > 2;
for(Card c : cards) {
if (c.getController().equals(p) && (c.isPlane() || c.isPhenomenon())) {
planarControllerLost = true;
}
- if (c.getOwner().equals(p)) {
- c.ceaseToExist();
- } else {
- c.removeTempController(p);
- if (c.getController().equals(p)) {
- this.getAction().exile(c, null);
+ if(isMultiplayer) {
+ if (c.getOwner().equals(p)) {
+ c.ceaseToExist();
+ } else {
+ c.removeTempController(p);
+ if (c.getController().equals(p)) {
+ this.getAction().exile(c, null);
+ }
}
+ } else {
+ c.forceTurnFaceUp();
}
-
}
// 901.6: If the current planar controller would leave the game, instead the next player
@@ -789,11 +800,11 @@ public class Game {
public Multimap chooseCardsForAnte(final boolean matchRarity) {
Multimap anteed = ArrayListMultimap.create();
-
+
if (matchRarity) {
-
+
boolean onePlayerHasTimeShifted = false;
-
+
List validRarities = new ArrayList<>(Arrays.asList(CardRarity.values()));
for (final Player player : getPlayers()) {
final Set playerRarity = getValidRarities(player.getCardsIn(ZoneType.Library));
@@ -809,24 +820,24 @@ public class Game {
}
return anteed;
}
-
+
//If possible, don't ante basic lands
if (validRarities.size() > 1) {
validRarities.remove(CardRarity.BasicLand);
}
-
+
if (validRarities.contains(CardRarity.Special)) {
onePlayerHasTimeShifted = false;
}
-
+
CardRarity anteRarity = validRarities.get(MyRandom.getRandom().nextInt(validRarities.size()));
-
+
System.out.println("Rarity chosen for ante: " + anteRarity.name());
-
+
for (final Player player : getPlayers()) {
CardCollection library = new CardCollection(player.getCardsIn(ZoneType.Library));
CardCollection toRemove = new CardCollection();
-
+
//Remove all cards that aren't of the chosen rarity
for (Card card : library) {
if (onePlayerHasTimeShifted && card.getRarity() == CardRarity.Special) {
@@ -845,16 +856,16 @@ public class Game {
}
}
}
-
+
library.removeAll(toRemove);
-
+
if (library.size() > 0) { //Make sure that matches were found. If not, use the original method to choose antes
Card ante = library.get(MyRandom.getRandom().nextInt(library.size()));
anteed.put(player, ante);
} else {
chooseRandomCardsForAnte(player, anteed);
}
-
+
}
}
else {
@@ -892,8 +903,9 @@ public class Game {
}
public void clearCaches() {
- spabCache.clear();
- cardCache.clear();
+
+ lastStateBattlefield.clear();
+ lastStateGraveyard.clear();
//playerCache.clear();
}
@@ -914,4 +926,17 @@ public class Game {
}
return false;
}
+
+ public Player getControlVote() {
+ Player result = null;
+ long maxValue = 0;
+ for (Player p : getPlayers()) {
+ Long v = p.getHighestControlVote();
+ if (v != null && v > maxValue) {
+ maxValue = v;
+ result = p;
+ }
+ }
+ return result;
+ }
}
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index 16b67a2d4e7..6456b345b3d 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -36,7 +36,6 @@ import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType;
-import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.staticability.StaticAbility;
@@ -104,7 +103,7 @@ public class GameAction {
boolean wasFacedown = c.isFaceDown();
//Rule 110.5g: A token that has left the battlefield can't move to another zone
- if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Command)) {
+ if (c.isToken() && zoneFrom != null && !fromBattlefield) {
return c;
}
@@ -157,11 +156,6 @@ public class GameAction {
c.removeSVar("EndOfTurnLeavePlay");
}
- // Clean up temporary variables such as Sunburst value or announced PayX value
- if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) {
- c.clearTemporaryVars();
- }
-
if (fromBattlefield && !toBattlefield) {
c.getController().setRevolt(true);
}
@@ -173,7 +167,7 @@ public class GameAction {
// if to Battlefield and it is caused by an replacement effect,
// try to get previous LKI if able
- if (zoneTo.is(ZoneType.Battlefield)) {
+ if (toBattlefield) {
if (cause != null && cause.isReplacementAbility()) {
ReplacementEffect re = cause.getReplacementEffect();
if (ReplacementType.Moved.equals(re.getMode())) {
@@ -185,6 +179,10 @@ public class GameAction {
if (lastKnownInfo == null) {
lastKnownInfo = CardUtil.getLKICopy(c);
}
+
+ if (!lastKnownInfo.hasKeyword("Counters remain on CARDNAME as it moves to any zone other than a player's hand or library.") || zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library)) {
+ copied.clearCounters();
+ }
} else {
// if from Battlefield to Graveyard and Card does exist in LastStateBattlefield
// use that instead
@@ -233,17 +231,20 @@ public class GameAction {
for (final StaticAbility sa : copied.getStaticAbilities()) {
sa.setHostCard(copied);
}
- if (c.getName().equals("Skullbriar, the Walking Grave")) {
- copied.setCounters(c.getCounters());
- }
-
- // ensure that any leftover keyword/type changes are cleared in the state view
- copied.updateStateForView();
} else { //Token
copied = c;
}
}
+ // ensure that any leftover keyword/type changes are cleared in the state view
+ copied.updateStateForView();
+
+ // Clean up temporary variables such as Sunburst value or announced PayX value
+ if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) {
+ copied.clearTemporaryVars();
+ }
+
+
if (!suppress) {
if (zoneFrom == null) {
copied.getOwner().addInboundToken(copied);
@@ -389,7 +390,8 @@ public class GameAction {
// play the change zone sound
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));
- final Map runParams = AbilityKey.mapFromCard(lastKnownInfo);
+ final Map runParams = AbilityKey.mapFromCard(copied);
+ runParams.put(AbilityKey.CardLKI, lastKnownInfo);
runParams.put(AbilityKey.Cause, cause);
runParams.put(AbilityKey.Origin, zoneFrom != null ? zoneFrom.getZoneType().name() : null);
runParams.put(AbilityKey.Destination, zoneTo.getZoneType().name());
@@ -419,13 +421,6 @@ public class GameAction {
return copied;
}
- // remove all counters from the card if destination is not the battlefield
- // UNLESS we're dealing with Skullbriar, the Walking Grave
- if (!c.isToken() && (zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library) ||
- (!toBattlefield && !c.getName().equals("Skullbriar, the Walking Grave")))) {
- copied.clearCounters();
- }
-
if (!c.isToken() && !toBattlefield) {
copied.clearDevoured();
copied.clearDelved();
@@ -547,6 +542,14 @@ public class GameAction {
c.setCastSA(null);
} else if (zoneTo.is(ZoneType.Stack)) {
c.setCastFrom(zoneFrom.getZoneType());
+ if (cause != null && cause.isSpell() && c.equals(cause.getHostCard()) && !c.isCopiedSpell()) {
+ cause.setLastStateBattlefield(game.getLastStateBattlefield());
+ cause.setLastStateGraveyard(game.getLastStateGraveyard());
+
+ c.setCastSA(cause);
+ } else {
+ c.setCastSA(null);
+ }
} else if (!(zoneTo.is(ZoneType.Battlefield) && zoneFrom.is(ZoneType.Stack))) {
c.setCastFrom(null);
c.setCastSA(null);
@@ -962,6 +965,9 @@ public class GameAction {
for (final Player p : game.getPlayers()) {
for (final ZoneType zt : ZoneType.values()) {
+ if (zt == ZoneType.Command)
+ p.checkKeywordCard();
+
if (zt == ZoneType.Battlefield) {
continue;
}
@@ -985,7 +991,7 @@ public class GameAction {
checkAgain = true;
} else if (c.hasKeyword("CARDNAME can't be destroyed by lethal damage unless lethal damage dealt by a single source is marked on it.")) {
for (final Integer dmg : c.getReceivedDamageFromThisTurn().values()) {
- if (c.getNetToughness() <= dmg.intValue()) {
+ if (c.getLethal() <= dmg.intValue()) {
if (desCreats == null) {
desCreats = new CardCollection();
}
@@ -997,7 +1003,7 @@ public class GameAction {
}
// Rule 704.5g - Destroy due to lethal damage
// Rule 704.5h - Destroy due to deathtouch
- else if (c.getNetToughness() <= c.getDamage() || c.hasBeenDealtDeathtouchDamage()) {
+ else if (c.getDamage() > 0 && (c.getLethal() <= c.getDamage() || c.hasBeenDealtDeathtouchDamage())) {
if (desCreats == null) {
desCreats = new CardCollection();
}
@@ -1017,8 +1023,10 @@ public class GameAction {
checkAgain |= stateBasedAction704_5r(c); // annihilate +1/+1 counters with -1/-1 ones
- if (c.getCounters(CounterType.DREAM) > 7 && c.hasKeyword("CARDNAME can't have more than seven dream counters on it.")) {
- c.subtractCounter(CounterType.DREAM, c.getCounters(CounterType.DREAM) - 7);
+ final CounterType dreamType = CounterType.get(CounterEnumType.DREAM);
+
+ if (c.getCounters(dreamType) > 7 && c.hasKeyword("CARDNAME can't have more than seven dream counters on it.")) {
+ c.subtractCounter(dreamType, c.getCounters(dreamType) - 7);
checkAgain = true;
}
}
@@ -1110,7 +1118,7 @@ public class GameAction {
if (!c.canBeSacrificed()) {
return false;
}
- if (c.getCounters(CounterType.LORE) < c.getFinalChapterNr()) {
+ if (c.getCounters(CounterEnumType.LORE) < c.getFinalChapterNr()) {
return false;
}
if (!game.getStack().hasSimultaneousStackEntries() &&
@@ -1151,16 +1159,18 @@ public class GameAction {
private boolean stateBasedAction704_5r(Card c) {
boolean checkAgain = false;
- int plusOneCounters = c.getCounters(CounterType.P1P1);
- int minusOneCounters = c.getCounters(CounterType.M1M1);
+ final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
+ final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
+ int plusOneCounters = c.getCounters(p1p1);
+ int minusOneCounters = c.getCounters(m1m1);
if (plusOneCounters > 0 && minusOneCounters > 0) {
int remove = Math.min(plusOneCounters, minusOneCounters);
// If a permanent has both a +1/+1 counter and a -1/-1 counter on it,
// N +1/+1 and N -1/-1 counters are removed from it, where N is the
// smaller of the number of +1/+1 and -1/-1 counters on it.
// This should fire remove counters trigger
- c.subtractCounter(CounterType.P1P1, remove);
- c.subtractCounter(CounterType.M1M1, remove);
+ c.subtractCounter(p1p1, remove);
+ c.subtractCounter(m1m1, remove);
checkAgain = true;
}
return checkAgain;
@@ -1171,7 +1181,7 @@ public class GameAction {
boolean checkAgain = false;
if (c.isToken()) {
final Zone zoneFrom = game.getZoneOf(c);
- if (!zoneFrom.is(ZoneType.Battlefield) && !zoneFrom.is(ZoneType.Command)) {
+ if (!zoneFrom.is(ZoneType.Battlefield)) {
zoneFrom.remove(c);
checkAgain = true;
}
@@ -1281,7 +1291,7 @@ public class GameAction {
//final Multimap uniqueWalkers = ArrayListMultimap.create(); // Not used as of Ixalan
for (Card c : list) {
- if (c.getCounters(CounterType.LOYALTY) <= 0) {
+ if (c.getCounters(CounterEnumType.LOYALTY) <= 0) {
sacrificeDestroy(c, null, table);
// Play the Destroy sound
game.fireEvent(new GameEventCardDestroyed());
@@ -1341,7 +1351,8 @@ public class GameAction {
recheck = true;
- Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)");
+ Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, null, p),
+ "You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
for (Card c: cc) {
if (c != toKeep) {
sacrificeDestroy(c, null, table);
@@ -1453,12 +1464,18 @@ public class GameAction {
revealTo(card, Collections.singleton(to));
}
public void revealTo(final CardCollectionView cards, final Player to) {
- revealTo(cards, Collections.singleton(to));
+ revealTo(cards, to, null);
+ }
+ public void revealTo(final CardCollectionView cards, final Player to, String messagePrefix) {
+ revealTo(cards, Collections.singleton(to), messagePrefix);
}
public void revealTo(final Card card, final Iterable to) {
revealTo(new CardCollection(card), to);
}
public void revealTo(final CardCollectionView cards, final Iterable to) {
+ revealTo(cards, to, null);
+ }
+ public void revealTo(final CardCollectionView cards, final Iterable to, String messagePrefix) {
if (cards.isEmpty()) {
return;
}
@@ -1466,7 +1483,7 @@ public class GameAction {
final ZoneType zone = cards.getFirst().getZone().getZoneType();
final Player owner = cards.getFirst().getOwner();
for (final Player p : to) {
- p.getController().reveal(cards, zone, owner);
+ p.getController().reveal(cards, zone, owner, messagePrefix);
}
}
diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java
index b54c727370f..68c756c804d 100644
--- a/forge-game/src/main/java/forge/game/GameActionUtil.java
+++ b/forge-game/src/main/java/forge/game/GameActionUtil.java
@@ -22,19 +22,28 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
+import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostParser;
+import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.card.CardPlayOption.PayManaCost;
import forge.game.cost.Cost;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.player.PlayerController;
+import forge.game.replacement.ReplacementEffect;
+import forge.game.replacement.ReplacementHandler;
+import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.*;
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;
@@ -210,23 +219,34 @@ public final class GameActionUtil {
}
}
- if (!sa.isBasicSpell()) {
- return alternatives;
- }
-
+ // below are for some special cases of activated abilities
if (sa.isCycling() && activator.hasKeyword("CyclingForZero")) {
- // set the cost to this directly to buypass non mana cost
- final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
- newSA.setActivatingPlayer(activator);
- newSA.setBasicSpell(false);
- newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0"));
- // makes new SpellDescription
- final StringBuilder sb = new StringBuilder();
- sb.append(newSA.getCostDescription());
- sb.append(newSA.getParam("SpellDescription"));
- newSA.setDescription(sb.toString());
- alternatives.add(newSA);
+ for (final KeywordInterface inst : source.getKeywords()) {
+ // need to find the correct Keyword from which this Ability is from
+ if (!inst.getAbilities().contains(sa)) {
+ continue;
+ }
+
+ // set the cost to this directly to buypass non mana cost
+ final SpellAbility newSA = sa.copyWithDefinedCost("Discard<1/CARDNAME>");
+ newSA.setActivatingPlayer(activator);
+ newSA.getMapParams().put("CostDesc", ManaCostParser.parse("0"));
+
+ // need to build a new Keyword to get better Reminder Text
+ String data[] = inst.getOriginal().split(":");
+ data[1] = "0";
+ KeywordInterface newKi = Keyword.getInstance(StringUtils.join(data, ":"));
+
+ // makes new SpellDescription
+ final StringBuilder sb = new StringBuilder();
+ sb.append(newSA.getCostDescription());
+ sb.append("(").append(newKi.getReminderText()).append(")");
+ newSA.setDescription(sb.toString());
+
+ alternatives.add(newSA);
+ break;
+ }
}
if (sa.hasParam("Equip") && activator.hasKeyword("EquipInstantSpeed")) {
@@ -363,10 +383,11 @@ public final class GameActionUtil {
}
SpellAbility result = null;
final Card host = sa.getHostCard();
+ final Game game = host.getGame();
final Player activator = sa.getActivatingPlayer();
final PlayerController pc = activator.getController();
- host.getGame().getAction().checkStaticAbilities(false);
+ game.getAction().checkStaticAbilities(false);
boolean reset = false;
@@ -429,7 +450,59 @@ public final class GameActionUtil {
int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE);
if (v > 0) {
- host.addReplacementEffect(CardFactoryUtil.makeEtbCounter("etbCounter:P1P1:" + v, host, false));
+
+ 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);
+
if (result == null) {
result = sa.copy();
}
@@ -513,14 +586,8 @@ public final class GameActionUtil {
// Mark SAs with subAbilities as undoable. These are generally things like damage, and other stuff
// that's hard to track and remove
sa.setUndoable(false);
- } else {
- try {
- if ((sa.getParam("Amount") != null) && (amount != Integer.parseInt(sa.getParam("Amount")))) {
- sa.setUndoable(false);
- }
- } catch (final NumberFormatException n) {
- sa.setUndoable(false);
- }
+ } else if ((sa.getParam("Amount") != null) && (amount != AbilityUtils.calculateAmount(sa.getHostCard(),sa.getParam("Amount"), sa))) {
+ sa.setUndoable(false);
}
final StringBuilder sb = new StringBuilder();
diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java
index f042378ec64..cbd00a9144f 100644
--- a/forge-game/src/main/java/forge/game/GameEntity.java
+++ b/forge-game/src/main/java/forge/game/GameEntity.java
@@ -24,6 +24,7 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
+import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.event.GameEventCardAttachment;
import forge.game.keyword.Keyword;
@@ -38,6 +39,7 @@ import forge.util.collect.FCollection;
import java.util.Map;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -48,7 +50,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
private int preventNextDamage = 0;
protected CardCollection attachedCards;
private Map> preventionShieldsWithEffects = Maps.newTreeMap();
- protected Map counters = Maps.newEnumMap(CounterType.class);
+ protected Map counters = Maps.newTreeMap();
protected GameEntity(int id0) {
id = id0;
@@ -315,7 +317,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
}
// enchanted means attached by Aura
- return CardLists.count(attachedCards, CardPredicates.Presets.AURA) > 0;
+ return Iterables.any(attachedCards, CardPredicates.Presets.AURA);
}
public final boolean hasCardAttachment(Card c) {
@@ -453,6 +455,10 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
return value == null ? 0 : value;
}
+ public final int getCounters(final CounterEnumType counterType) {
+ return getCounters(CounterType.get(counterType));
+ }
+
public void setCounters(final CounterType counterType, final Integer num) {
if (num <= 0) {
counters.remove(counterType);
@@ -461,6 +467,10 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
}
}
+ public void setCounters(final CounterEnumType counterType, final Integer num) {
+ setCounters(CounterType.get(counterType), num);
+ }
+
abstract public void setCounters(final Map allCounters);
abstract public boolean canReceiveCounters(final CounterType type);
@@ -468,6 +478,16 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
abstract public void subtractCounter(final CounterType counterName, final int n);
abstract public void clearCounters();
+ public boolean canReceiveCounters(final CounterEnumType type) {
+ return canReceiveCounters(CounterType.get(type));
+ }
+
+ public int addCounter(final CounterEnumType counterType, final int n, final Player source, final boolean applyMultiplier, final boolean fireEvents, GameEntityCounterTable table) {
+ return addCounter(CounterType.get(counterType), n, source, applyMultiplier, fireEvents, table);
+ }
+ public void subtractCounter(final CounterEnumType counterName, final int n) {
+ subtractCounter(CounterType.get(counterName), n);
+ }
@Override
public final boolean equals(Object o) {
diff --git a/forge-game/src/main/java/forge/game/GameEntityCache.java b/forge-game/src/main/java/forge/game/GameEntityCache.java
index b5532f2bf96..de4716be035 100644
--- a/forge-game/src/main/java/forge/game/GameEntityCache.java
+++ b/forge-game/src/main/java/forge/game/GameEntityCache.java
@@ -13,6 +13,11 @@ public class GameEntityCache entities) {
+ for (Entity e : entities) {
+ put(e.getId(), e);
+ }
+ }
public void remove(Integer id) {
entityCache.remove(id);
diff --git a/forge-game/src/main/java/forge/game/GameEntityView.java b/forge-game/src/main/java/forge/game/GameEntityView.java
index 0349b4c8e54..5684e41d424 100644
--- a/forge-game/src/main/java/forge/game/GameEntityView.java
+++ b/forge-game/src/main/java/forge/game/GameEntityView.java
@@ -24,6 +24,12 @@ public abstract class GameEntityView extends TrackableObject {
return collection;
}
+ public static GameEntityViewMap getMap(Iterable spabs) {
+ GameEntityViewMap gameViewCache = new GameEntityViewMap();
+ gameViewCache.putAll(spabs);
+ return gameViewCache;
+ }
+
protected GameEntityView(final int id0, final Tracker tracker) {
super(id0, tracker);
}
diff --git a/forge-game/src/main/java/forge/game/GameEntityViewMap.java b/forge-game/src/main/java/forge/game/GameEntityViewMap.java
new file mode 100644
index 00000000000..e30161405bc
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/GameEntityViewMap.java
@@ -0,0 +1,52 @@
+package forge.game;
+
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.ForwardingMap;
+import com.google.common.collect.Maps;
+
+import forge.trackable.TrackableCollection;
+
+public class GameEntityViewMap extends ForwardingMap {
+ private Map dataMap = Maps.newLinkedHashMap();
+
+ @Override
+ protected Map delegate() {
+ return dataMap;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void put(Entity e) {
+ this.put((View) e.getView(), e);
+ }
+
+ public void putAll(Iterable entities) {
+ for (Entity e : entities) {
+ put(e);
+ }
+ }
+
+ public void remove(Entity e) {
+ this.remove(e.getView());
+ }
+
+ public void removeAll(Iterable entities) {
+ for (Entity e : entities) {
+ remove(e);
+ }
+ }
+
+ public void addToList(Iterable views, List list) {
+ for (View view : views) {
+ Entity entity = get(view);
+ if (entity != null) {
+ list.add(entity);
+ }
+ }
+ }
+
+ public TrackableCollection getTrackableKeys() {
+ return new TrackableCollection(this.keySet());
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/GameFormat.java b/forge-game/src/main/java/forge/game/GameFormat.java
index e4efda21fb6..a66395ce300 100644
--- a/forge-game/src/main/java/forge/game/GameFormat.java
+++ b/forge-game/src/main/java/forge/game/GameFormat.java
@@ -47,7 +47,7 @@ import java.util.Map.Entry;
public class GameFormat implements Comparable {
private final String name;
public enum FormatType {Sanctioned, Casual, Historic, Digital, Custom}
- public enum FormatSubType {Block, Standard, Extended, Pioneer, Modern, Legacy, Vintage, Commander, Planechase, Videogame, MTGO, Custom}
+ public enum FormatSubType {Block, Standard, Extended, Pioneer, Modern, Legacy, Vintage, Commander, Planechase, Videogame, MTGO, Arena, Custom}
// contains allowed sets, when empty allows all sets
private FormatType formatType;
diff --git a/forge-game/src/main/java/forge/game/GameLogFormatter.java b/forge-game/src/main/java/forge/game/GameLogFormatter.java
index f098cb088e7..cb83ff742e6 100644
--- a/forge-game/src/main/java/forge/game/GameLogFormatter.java
+++ b/forge-game/src/main/java/forge/game/GameLogFormatter.java
@@ -95,7 +95,7 @@ public class GameLogFormatter extends IGameEventVisitor.Base {
String action = event.sa.isSpell() ? localizer.getMessage("lblCast")
: event.sa.isTrigger() ? localizer.getMessage("lblTriggered")
: localizer.getMessage("lblActivated");
- String object = event.sa.getStackDescription().startsWith("Morph ")
+ String object = event.si.getStackDescription().startsWith("Morph ")
? localizer.getMessage("lblMorph")
: event.sa.getHostCard().toString();
diff --git a/forge-game/src/main/java/forge/game/GameView.java b/forge-game/src/main/java/forge/game/GameView.java
index 0aad79f5cba..eda68175be6 100644
--- a/forge-game/src/main/java/forge/game/GameView.java
+++ b/forge-game/src/main/java/forge/game/GameView.java
@@ -24,7 +24,6 @@ import java.util.List;
public class GameView extends TrackableObject {
private static final long serialVersionUID = 8522884512960961528L;
- private CombatView combatView;
private final transient Game game; //TODO: Remove this when possible before network support added
public GameView(final Game game0) {
@@ -140,15 +139,18 @@ public class GameView extends TrackableObject {
}
public CombatView getCombat() {
- return combatView;
+ return get(TrackableProperty.CombatView);
+ }
+ public void updateCombatView(CombatView combatView) {
+ set(TrackableProperty.CombatView, combatView);
}
void updateCombat(Combat combat) {
if (combat == null) {
- combatView = null;
+ set(TrackableProperty.CombatView, null);
return;
}
- combatView = new CombatView(combat.getAttackingPlayer().getGame().getTracker());
+ final CombatView combatView = new CombatView(combat.getAttackingPlayer().getGame().getTracker());
for (final AttackingBand b : combat.getAttackingBands()) {
if (b == null) continue;
final GameEntity defender = combat.getDefenderByAttacker(b);
@@ -160,6 +162,7 @@ public class GameView extends TrackableObject {
isBlocked ? CardView.getCollection(blockers) : null,
CardView.getCollection(blockers));
}
+ updateCombatView(combatView);
}
public void serialize() {
diff --git a/forge-game/src/main/java/forge/game/Match.java b/forge-game/src/main/java/forge/game/Match.java
index 1ef3b279a23..59b99b724ee 100644
--- a/forge-game/src/main/java/forge/game/Match.java
+++ b/forge-game/src/main/java/forge/game/Match.java
@@ -183,7 +183,7 @@ public class Match {
return myRemovedAnteCards;
}
- private static void preparePlayerLibrary(Player player, final ZoneType zoneType, CardPool section, boolean canRandomFoil) {
+ private static void preparePlayerZone(Player player, final ZoneType zoneType, CardPool section, boolean canRandomFoil) {
PlayerZone library = player.getZone(zoneType);
List newLibrary = new ArrayList<>();
for (final Entry stackOfCards : section) {
@@ -234,16 +234,15 @@ public class Match {
for (int i = 0; i < playersConditions.size(); i++) {
final Player player = players.get(i);
final RegisteredPlayer psc = playersConditions.get(i);
+ PlayerController person = player.getController();
if (canSideBoard) {
- PlayerController person = player.getController();
if (sideboardProxy != null && person.isAI()) {
person = sideboardProxy;
}
- String forPlayer = " for " + player.getName();
Deck toChange = psc.getDeck();
- List newMain = person.sideboard(toChange, rules.getGameType(), forPlayer);
+ List newMain = person.sideboard(toChange, rules.getGameType(), player.getName());
if (null != newMain) {
CardPool allCards = new CardPool();
allCards.addAll(toChange.get(DeckSection.Main));
@@ -270,21 +269,37 @@ public class Match {
}
}
- preparePlayerLibrary(player, ZoneType.Library, myDeck.getMain(), psc.useRandomFoil());
+ preparePlayerZone(player, ZoneType.Library, myDeck.getMain(), psc.useRandomFoil());
if (myDeck.has(DeckSection.Sideboard)) {
- preparePlayerLibrary(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil());
+ preparePlayerZone(player, ZoneType.Sideboard, myDeck.get(DeckSection.Sideboard), psc.useRandomFoil());
+
+ // Assign Companion
+ Card companion = player.assignCompanion(game, person);
+ // Create an effect that lets you cast your companion from your sideboard
+ if (companion != null) {
+ PlayerZone commandZone = player.getZone(ZoneType.Command);
+ companion = game.getAction().moveTo(ZoneType.Command, companion, null);
+ commandZone.add(Player.createCompanionEffect(game, companion));
+
+ player.updateZoneForView(commandZone);
+ }
}
player.initVariantsZones(psc);
player.shuffle(null);
-
if (isFirstGame) {
Collection extends PaperCard> cardsComplained = player.getController().complainCardsCantPlayWell(myDeck);
if (null != cardsComplained) {
rAICards.putAll(player, cardsComplained);
}
+ } else {
+ //reset cards to fix weird issues on netplay nextgame client
+ for (Card c : player.getCardsIn(ZoneType.Library)) {
+ c.setTapped(false);
+ c.resetActivationsPerTurn();
+ }
}
if (myRemovedAnteCards != null && !myRemovedAnteCards.isEmpty()) {
diff --git a/forge-game/src/main/java/forge/game/StaticEffect.java b/forge-game/src/main/java/forge/game/StaticEffect.java
index dc9d51094a0..d6ce1813d7e 100644
--- a/forge-game/src/main/java/forge/game/StaticEffect.java
+++ b/forge-game/src/main/java/forge/game/StaticEffect.java
@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
@@ -37,7 +37,7 @@ import com.google.common.collect.Maps;
*
* StaticEffect class.
*
- *
+ *
* @author Forge
* @version $Id$
*/
@@ -72,7 +72,7 @@ public class StaticEffect {
/**
* setTimestamp TODO Write javadoc for this method.
- *
+ *
* @param t
* a long
*/
@@ -82,7 +82,7 @@ public class StaticEffect {
/**
* getTimestamp. TODO Write javadoc for this method.
- *
+ *
* @return a long
*/
public final long getTimestamp() {
@@ -93,7 +93,7 @@ public class StaticEffect {
*
* Getter for the field source.
*
- *
+ *
* @return a {@link forge.game.card.Card} object.
*/
public final Card getSource() {
@@ -104,7 +104,7 @@ public class StaticEffect {
*
* Getter for the field affectedCards.
*
- *
+ *
* @return a {@link forge.CardList} object.
*/
public final CardCollectionView getAffectedCards() {
@@ -115,7 +115,7 @@ public class StaticEffect {
*
* Setter for the field affectedCards.
*
- *
+ *
* @param list
* a {@link forge.CardList} object.
*/
@@ -125,7 +125,7 @@ public class StaticEffect {
/**
* Gets the affected players.
- *
+ *
* @return the affected players
*/
public final List getAffectedPlayers() {
@@ -134,7 +134,7 @@ public class StaticEffect {
/**
* Sets the affected players.
- *
+ *
* @param list
* the new affected players
*/
@@ -144,7 +144,7 @@ public class StaticEffect {
/**
* setParams. TODO Write javadoc for this method.
- *
+ *
* @param params
* a HashMap
*/
@@ -154,7 +154,7 @@ public class StaticEffect {
/**
* Gets the params.
- *
+ *
* @return the params
*/
public final Map getParams() {
@@ -171,13 +171,12 @@ public class StaticEffect {
/**
* Undo everything that was changed by this effect.
- *
+ *
* @return a {@link CardCollectionView} of all affected cards.
*/
final CardCollectionView remove() {
final CardCollectionView affectedCards = getAffectedCards();
final List affectedPlayers = getAffectedPlayers();
- //final Map params = getParams();
String changeColorWordsTo = null;
@@ -245,6 +244,10 @@ public class StaticEffect {
p.removeMaxLandPlays(getTimestamp());
p.removeMaxLandPlaysInfinite(getTimestamp());
+
+ p.removeControlVote(getTimestamp());
+ p.removeAdditionalVote(getTimestamp());
+ p.removeAdditionalOptionalVote(getTimestamp());
}
// modify the affected card
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 55bdfd789e6..7607398cd73 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java
@@ -247,20 +247,7 @@ public final class AbilityFactory {
if (mapParams.containsKey("SubAbility")) {
final String name = mapParams.get("SubAbility");
- SpellAbility p = parent;
- AbilitySub sub = null;
- while (p != null) {
- sub = p.getAdditionalAbility(name);
- if (sub != null) {
- break;
- }
- p = p.getParent();
- }
- if (sub == null) {
- sub = getSubAbility(state, name, spellAbility);
- }
- spellAbility.setSubAbility(sub);
- spellAbility.setAdditionalAbility(name, sub);
+ spellAbility.setSubAbility(getSubAbility(state, name, spellAbility));
}
for (final String key : additionalAbilityKeys) {
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 f5b40e4ca3a..cc160872bf6 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
@@ -13,6 +13,7 @@ import forge.card.mana.ManaCostParser;
import forge.card.mana.ManaCostShard;
import forge.game.CardTraitBase;
import forge.game.Game;
+import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.ability.AbilityFactory.AbilityRecordType;
import forge.game.card.*;
@@ -25,7 +26,10 @@ import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.*;
+import forge.game.trigger.Trigger;
+import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
+import forge.util.Aggregates;
import forge.util.Expressions;
import forge.util.TextUtil;
import forge.util.collect.FCollection;
@@ -43,15 +47,16 @@ import java.util.regex.Pattern;
public class AbilityUtils {
- private final static ImmutableList cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE");
+ private final static ImmutableList cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE");
public static CounterType getCounterType(String name, SpellAbility sa) throws Exception {
CounterType counterType;
if ("ReplacedCounterType".equals(name)) {
- name = (String) sa.getReplacingObject(AbilityKey.CounterType);
+ name = (String) sa.getReplacingObject(AbilityKey.CounterType);
}
- try {
+ //try {
counterType = CounterType.getType(name);
+ /*
} catch (Exception e) {
String type = sa.getSVar(name);
if (type.equals("")) {
@@ -63,6 +68,7 @@ public class AbilityUtils {
}
counterType = CounterType.getType(type);
}
+ //*/
return counterType;
}
@@ -202,6 +208,17 @@ public class AbilityUtils {
cards.add((Card) o);
}
}
+ } else if (defined.equals("DelayTriggerRememberedLKI")) {
+ SpellAbility trigSa = sa.getTriggeringAbility();
+ if (trigSa != null) {
+ for (Object o : trigSa.getTriggerRemembered()) {
+ if (o instanceof Card) {
+ cards.add((Card)o);
+ }
+ }
+ } else {
+ System.err.println("Warning: couldn't find trigger SA in the chain of SpellAbility " + sa);
+ }
} else if (defined.equals("DelayTriggerRemembered")) {
SpellAbility trigSa = sa.getTriggeringAbility();
if (trigSa != null) {
@@ -270,6 +287,8 @@ public class AbilityUtils {
list = sa.getRootAbility().getPaidList("ExiledCards");
} else if (defined.startsWith("Exiled")) {
list = sa.getRootAbility().getPaidList("Exiled");
+ } else if (defined.startsWith("Milled")) {
+ list = sa.getRootAbility().getPaidList("Milled");
} else if (defined.startsWith("TappedCards")) {
list = sa.getRootAbility().getPaidList("TappedCards");
} else if (defined.startsWith("Tapped")) {
@@ -696,6 +715,16 @@ public class AbilityUtils {
list = null;
}
}
+ else if (calcX[0].startsWith("TriggerRemembered")) {
+ final SpellAbility root = sa.getRootAbility();
+ CardCollection result = new CardCollection();
+ for (Object o : root.getTriggerRemembered()) {
+ if (o instanceof Card) {
+ result.add((Card) o);
+ }
+ }
+ list = result;
+ }
else if (calcX[0].startsWith("TriggerObjects")) {
final SpellAbility root = sa.getRootAbility();
list = (CardCollection) root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(14)));
@@ -751,9 +780,18 @@ public class AbilityUtils {
final FCollection objects = new FCollection<>();
final String defined = (def == null) ? "Self" : def;
- objects.addAll(AbilityUtils.getDefinedPlayers(card, defined, sa));
+ objects.addAll(getDefinedPlayers(card, defined, sa));
+ objects.addAll(getDefinedCards(card, defined, sa));
+ objects.addAll(getDefinedSpellAbilities(card, defined, sa));
+ return objects;
+ }
+
+ public static FCollection getDefinedEntities(final Card card, final String def, final SpellAbility sa) {
+ final FCollection objects = new FCollection<>();
+ final String defined = (def == null) ? "Self" : def;
+
+ objects.addAll(getDefinedPlayers(card, defined, sa));
objects.addAll(getDefinedCards(card, defined, sa));
- objects.addAll(AbilityUtils.getDefinedSpellAbilities(card, defined, sa));
return objects;
}
@@ -878,8 +916,8 @@ public class AbilityUtils {
}
}
if (sa.hasParam("AbilityCount")) { // replace specific string other than "EQ" cases
- String var = sa.getParam("AbilityCount");
- valid = TextUtil.fastReplace(valid, var, Integer.toString(calculateAmount(source, var, sa)));
+ String var = sa.getParam("AbilityCount");
+ valid = TextUtil.fastReplace(valid, var, Integer.toString(calculateAmount(source, var, sa)));
}
return CardLists.getValidCards(list, valid.split(","), sa.getActivatingPlayer(), source, sa);
}
@@ -1187,7 +1225,7 @@ public class AbilityUtils {
}
}
else if (defined.equals("ActivePlayer")) {
- players.add(game.getPhaseHandler().getPlayerTurn());
+ players.add(game.getPhaseHandler().getPlayerTurn());
}
else if (defined.equals("You")) {
players.add(player);
@@ -1321,7 +1359,7 @@ public class AbilityUtils {
Player pl = sa.getActivatingPlayer();
final Game game = pl.getGame();
- if (sa.isTrigger() && sa.getParent() == null && sa.getPayCosts() != null) {
+ if (sa.isTrigger() && sa.getParent() == null) {
// when trigger cost are paid before the effect does resolve, need to clean the trigger
game.getTriggerHandler().resetActiveTriggers();
}
@@ -1416,11 +1454,11 @@ public class AbilityUtils {
}
}
else if (unlessCost.equals("ChosenManaCost")) {
- if (!source.hasChosenCard()) {
+ if (!source.hasChosenCard()) {
cost = new Cost(ManaCost.ZERO, true);
}
- else {
- cost = new Cost(Iterables.getFirst(source.getChosenCards(), null).getManaCost(), true);
+ else {
+ cost = new Cost(Iterables.getFirst(source.getChosenCards(), null).getManaCost(), true);
}
}
else if (unlessCost.equals("ChosenNumber")) {
@@ -1573,11 +1611,55 @@ public class AbilityUtils {
// special logic for xPaid in SpellAbility
if (sq[0].contains("xPaid")) {
- // ETB effects of cloned cards have xPaid = 0
- if (sa.hasParam("ETB") && sa.getOriginalHost() != null) {
- return 0;
+ SpellAbility root = sa.getRootAbility();
+
+ // 107.3i If an object gains an ability, the value of X within that ability is the value defined by that ability,
+ // or 0 if that ability doesn’t define a value of X. This is an exception to rule 107.3h. This may occur with ability-adding effects, text-changing effects, or copy effects.
+ if (root.getXManaCostPaid() != null) {
+ return CardFactoryUtil.doXMath(root.getXManaCostPaid(), expr, c);
}
- return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
+
+ // If the chosen creature has X in its mana cost, that X is considered to be 0.
+ // The value of X in Altered Ego’s last ability will be whatever value was chosen for X while casting Altered Ego.
+ if (sa.getOriginalHost() != null || !sa.getHostCard().equals(c)) {
+ return CardFactoryUtil.doXMath(0, expr, c);
+ }
+
+ if (root.isTrigger()) {
+ Trigger t = root.getTrigger();
+ if (t == null) {
+ return CardFactoryUtil.doXMath(0, expr, c);
+ }
+
+ // 107.3k If an object’s enters-the-battlefield triggered ability or replacement effect refers to X,
+ // and the spell that became that object as it resolved had a value of X chosen for any of its costs,
+ // the value of X for that ability is the same as the value of X for that spell, although the value of X for that permanent is 0.
+ if (TriggerType.ChangesZone.equals(t.getMode())
+ && ZoneType.Battlefield.name().equals(t.getParam("Destination"))) {
+ return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
+ } else if (TriggerType.SpellCast.equals(t.getMode())) {
+ // Cast Trigger like Hydroid Krasis
+ SpellAbility castSA = (SpellAbility) root.getTriggeringObject(AbilityKey.SpellAbility);
+ if (castSA == null || castSA.getXManaCostPaid() == null) {
+ return CardFactoryUtil.doXMath(0, expr, c);
+ }
+ return CardFactoryUtil.doXMath(castSA.getXManaCostPaid(), expr, c);
+ } else if (TriggerType.Cycled.equals(t.getMode())) {
+ SpellAbility cycleSA = (SpellAbility) sa.getTriggeringObject(AbilityKey.Cause);
+ if (cycleSA == null || cycleSA.getXManaCostPaid() == null) {
+ return CardFactoryUtil.doXMath(0, expr, c);
+ }
+ return CardFactoryUtil.doXMath(cycleSA.getXManaCostPaid(), expr, c);
+ }
+ }
+
+ if (root.isReplacementAbility()) {
+ if (sa.hasParam("ETB")) {
+ return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
+ }
+ }
+
+ return CardFactoryUtil.doXMath(0, expr, c);
}
// Count$Kicked..
@@ -1652,22 +1734,26 @@ public class AbilityUtils {
if (l[0].startsWith("LastStateBattlefield")) {
final String[] k = l[0].split(" ");
CardCollectionView list = null;
- if (sa.getLastStateBattlefield() != null && !sa.getLastStateBattlefield().isEmpty()) {
- list = new CardCollection(sa.getLastStateBattlefield());
+ if (sa.getLastStateBattlefield() != null) {
+ list = sa.getLastStateBattlefield();
} else { // LastState is Empty
- list = sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield);
+ return CardFactoryUtil.doXMath(0, expr, c);
}
list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa);
- return CardFactoryUtil.doXMath(list.size(), expr, c);
+ if (k[0].contains("TotalToughness")) {
+ return CardFactoryUtil.doXMath(Aggregates.sum(list, CardPredicates.Accessors.fnGetNetToughness), expr, c);
+ } else {
+ return CardFactoryUtil.doXMath(list.size(), expr, c);
+ }
}
if (l[0].startsWith("LastStateGraveyard")) {
final String[] k = l[0].split(" ");
CardCollectionView list = null;
- if (sa.getLastStateGraveyard() != null && !sa.getLastStateGraveyard().isEmpty()) {
- list = new CardCollection(sa.getLastStateGraveyard());
+ if (sa.getLastStateGraveyard() != null) {
+ list = sa.getLastStateGraveyard();
} else { // LastState is Empty
- list = sa.getHostCard().getGame().getCardsIn(ZoneType.Graveyard);
+ return CardFactoryUtil.doXMath(0, expr, c);
}
list = CardLists.getValidCards(list, k[1].split(","), sa.getActivatingPlayer(), c, sa);
return CardFactoryUtil.doXMath(list.size(), expr, c);
@@ -1793,7 +1879,7 @@ public class AbilityUtils {
// Color should not replace itself.
if (e.getValue().equalsIgnoreCase(colorLowerCase)) {
continue;
- }
+ }
value = getReplacedText(colorLowerCase, e.getValue(), isDescriptive);
replaced = replaced.replaceAll("(?)" + colorLowerCase, value.toLowerCase());
value = getReplacedText(colorCaptCase, e.getValue(), isDescriptive);
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 e058a41c2c0..30f174e7da7 100644
--- a/forge-game/src/main/java/forge/game/ability/ApiType.java
+++ b/forge-game/src/main/java/forge/game/ability/ApiType.java
@@ -40,6 +40,7 @@ public enum ApiType {
ChooseCard (ChooseCardEffect.class),
ChooseColor (ChooseColorEffect.class),
ChooseDirection (ChooseDirectionEffect.class),
+ ChooseEvenOdd (ChooseEvenOddEffect.class),
ChooseNumber (ChooseNumberEffect.class),
ChoosePlayer (ChoosePlayerEffect.class),
ChooseSource (ChooseSourceEffect.class),
@@ -47,6 +48,7 @@ public enum ApiType {
Clash (ClashEffect.class),
Cleanup (CleanUpEffect.class),
Clone (CloneEffect.class),
+ CompanionChoose (ChooseCompanionEffect.class),
CopyPermanent (CopyPermanentEffect.class),
CopySpellAbility (CopySpellAbilityEffect.class),
ControlSpell (ControlSpellEffect.class),
@@ -88,6 +90,7 @@ public enum ApiType {
GenericChoice (ChooseGenericEffect.class),
Goad (GoadEffect.class),
Haunt (HauntEffect.class),
+ Investigate (InvestigateEffect.class),
ImmediateTrigger (ImmediateTriggerEffect.class),
LookAt (LookAtEffect.class),
LoseLife (LifeLoseEffect.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 d90b9c90396..ae557bfd3fe 100644
--- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
@@ -13,6 +13,7 @@ import com.google.common.collect.Lists;
import forge.GameCommand;
import forge.card.CardType;
import forge.game.Game;
+import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -34,7 +35,7 @@ import forge.util.collect.FCollection;
*
* AbilityFactory_AlterLife class.
*
- *
+ *
* @author Forge
* @version $Id: AbilityFactoryAlterLife.java 17656 2012-10-22 19:32:56Z Max mtg $
*/
@@ -93,7 +94,7 @@ public abstract class SpellAbilityEffect {
final String baseDesc = this.getStackDescription(sa);
if (conditionDesc != null) {
sb.append(conditionDesc).append(" ");
- }
+ }
sb.append(baseDesc);
}
@@ -112,9 +113,8 @@ public abstract class SpellAbilityEffect {
sb.append(" ");
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount))));
} else{
- if (sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null &&
- sa.getPayCosts().getCostMana().getAmountOfX() > 0) {
- int amount = sa.getHostCard().getXManaCostPaid();
+ if (sa.costHasManaX()) {
+ int amount = sa.getXManaCostPaid() == null ? 0 : sa.getXManaCostPaid();
sb.append(" ");
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace("X","=",String.valueOf(amount))));
}
@@ -126,7 +126,7 @@ public abstract class SpellAbilityEffect {
/**
* Append the description of a {@link SpellAbility} to a
* {@link StringBuilder}.
- *
+ *
* @param sa
* a {@link SpellAbility}.
* @param sb
@@ -174,7 +174,7 @@ public abstract class SpellAbilityEffect {
private static CardCollection getCards(final boolean definedFirst, final String definedParam, final SpellAbility sa) {
final boolean useTargets = sa.usesTargeting() && (!definedFirst || !sa.hasParam(definedParam));
- return useTargets ? new CardCollection(sa.getTargets().getTargetCards())
+ return useTargets ? new CardCollection(sa.getTargets().getTargetCards())
: AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam(definedParam), sa);
}
@@ -197,10 +197,22 @@ public abstract class SpellAbilityEffect {
private static List getSpells(final boolean definedFirst, final String definedParam, final SpellAbility sa) {
final boolean useTargets = sa.usesTargeting() && (!definedFirst || !sa.hasParam(definedParam));
- return useTargets ? Lists.newArrayList(sa.getTargets().getTargetSpells())
+ return useTargets ? Lists.newArrayList(sa.getTargets().getTargetSpells())
: AbilityUtils.getDefinedSpellAbilities(sa.getHostCard(), sa.getParam(definedParam), sa);
}
+
+ // Targets of card or player type
+ protected final static List getTargetEntities(final SpellAbility sa) { return getEntities(false, "Defined", sa); }
+ protected final static List getTargetEntities(final SpellAbility sa, final String definedParam) { return getEntities(false, definedParam, sa); }
+ protected final static List getDefinedEntitiesOrTargeted(SpellAbility sa, final String definedParam) { return getEntities(true, definedParam, sa); }
+
+ private static List getEntities(final boolean definedFirst, final String definedParam, final SpellAbility sa) {
+ final boolean useTargets = sa.usesTargeting() && (!definedFirst || !sa.hasParam(definedParam));
+ return useTargets ? Lists.newArrayList(sa.getTargets().getTargetEntities())
+ : AbilityUtils.getDefinedEntities(sa.getHostCard(), sa.getParam(definedParam), sa);
+ }
+
// Targets of unspecified type
protected final static List getTargets(final SpellAbility sa) { return getTargetables(false, "Defined", sa); }
protected final static List getTargets(final SpellAbility sa, final String definedParam) { return getTargetables(false, definedParam, sa); }
@@ -208,10 +220,10 @@ public abstract class SpellAbilityEffect {
private static List getTargetables(final boolean definedFirst, final String definedParam, final SpellAbility sa) {
final boolean useTargets = sa.usesTargeting() && (!definedFirst || !sa.hasParam(definedParam));
- return useTargets ? Lists.newArrayList(sa.getTargets().getTargets())
+ return useTargets ? Lists.newArrayList(sa.getTargets().getTargets())
: AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam(definedParam), sa);
}
-
+
protected static void registerDelayedTrigger(final SpellAbility sa, String location, final List crds) {
boolean intrinsic = sa.isIntrinsic();
@@ -219,14 +231,14 @@ public abstract class SpellAbilityEffect {
boolean combat = location.endsWith("Combat");
String desc = sa.hasParam("AtEOTDesc") ? sa.getParam("AtEOTDesc") : "";
-
+
if (your) {
location = location.substring("Your".length());
}
if (combat) {
location = location.substring(0, location.length() - "Combat".length());
}
-
+
if (desc.isEmpty()) {
StringBuilder sb = new StringBuilder();
if (location.equals("Hand")) {
@@ -254,12 +266,12 @@ public abstract class SpellAbilityEffect {
StringBuilder delTrig = new StringBuilder();
delTrig.append("Mode$ Phase | Phase$ ");
delTrig.append(combat ? "EndCombat " : "End Of Turn ");
-
+
if (your) {
delTrig.append("| ValidPlayer$ You ");
}
delTrig.append("| TriggerDescription$ ").append(desc);
-
+
final Trigger trig = TriggerHandler.parseTrigger(delTrig.toString(), sa.getHostCard(), intrinsic);
for (final Card c : crds) {
trig.addRemembered(c);
@@ -290,7 +302,7 @@ public abstract class SpellAbilityEffect {
trig.setOverridingAbility(newSa);
sa.getActivatingPlayer().getGame().getTriggerHandler().registerDelayedTrigger(trig);
}
-
+
protected static void addSelfTrigger(final SpellAbility sa, String location, final Card card) {
String trigStr = "Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield " +
@@ -312,17 +324,17 @@ public abstract class SpellAbilityEffect {
card.setSVar("EndOfTurnLeavePlay", "AtEOT");
}
}
-
+
protected static void addForgetOnMovedTrigger(final Card card, final String zone) {
String trig = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ " + zone + " | 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, card);
AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, card);
saForget.setSubAbility(saExile);
-
+
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true);
parsedTrigger.setOverridingAbility(saForget);
final Trigger addedTrigger = card.addTrigger(parsedTrigger);
@@ -337,22 +349,22 @@ public abstract class SpellAbilityEffect {
final Trigger addedTrigger = card.addTrigger(parsedTrigger);
addedTrigger.setIntrinsic(true);
}
-
+
protected static void addForgetCounterTrigger(final Card card, final String counterType) {
String trig = "Mode$ CounterRemoved | TriggerZones$ Command | ValidCard$ Card.IsRemembered | CounterType$ " + counterType + " | NewCounterAmount$ 0 | 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, card);
AbilitySub saExile = (AbilitySub) AbilityFactory.getAbility(exileEffect, card);
saForget.setSubAbility(saExile);
-
+
final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true);
parsedTrigger.setOverridingAbility(saForget);
final Trigger addedTrigger = card.addTrigger(parsedTrigger);
- addedTrigger.setIntrinsic(true);
+ addedTrigger.setIntrinsic(true);
}
protected static void addLeaveBattlefieldReplacement(final Card card, final SpellAbility sa, final String zone) {
@@ -379,10 +391,10 @@ public abstract class SpellAbilityEffect {
game.getAction().moveTo(ZoneType.Command, eff, sa);
game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
}
-
+
protected static void addLeaveBattlefieldReplacement(final Card eff, final String zone) {
final String repeffstr = "Event$ Moved | ValidCard$ Card.IsRemembered "
- + "| Origin$ Battlefield | ExcludeDestination$ " + zone
+ + "| Origin$ Battlefield | ExcludeDestination$ " + zone
+ "| Description$ If Creature would leave the battlefield, "
+ " exile it instead of putting it anywhere else.";
String effect = "DB$ ChangeZone | Defined$ ReplacedCard | Origin$ Battlefield | Destination$ " + zone;
@@ -393,7 +405,7 @@ public abstract class SpellAbilityEffect {
re.setOverridingAbility(AbilityFactory.getAbility(effect, eff));
eff.addReplacementEffect(re);
}
-
+
// create a basic template for Effect to be used somewhere else
protected static Card createEffect(final SpellAbility sa, final Player controller, final String name,
final String image) {
@@ -412,7 +424,6 @@ public abstract class SpellAbilityEffect {
}
}
}
- eff.setToken(true); // Set token to true, so when leaving play it gets nuked
eff.setOwner(controller);
eff.setImageKey(image);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AmassEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AmassEffect.java
index b7dd51336e9..1612c2ae3da 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/AmassEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/AmassEffect.java
@@ -1,16 +1,23 @@
package forge.game.ability.effects;
+import java.util.Map;
+
+import org.apache.commons.lang3.mutable.MutableBoolean;
+
+import com.google.common.collect.Maps;
+
import forge.game.Game;
import forge.game.GameEntityCounterTable;
import forge.game.ability.AbilityUtils;
-import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardZoneTable;
+import forge.game.card.CounterEnumType;
import forge.game.card.CounterType;
import forge.game.card.token.TokenInfo;
+import forge.game.event.GameEventCombatChanged;
import forge.game.event.GameEventTokenCreated;
import forge.game.player.Player;
import forge.game.player.PlayerController;
@@ -19,7 +26,7 @@ import forge.game.zone.ZoneType;
import forge.util.Lang;
import forge.util.Localizer;
-public class AmassEffect extends SpellAbilityEffect {
+public class AmassEffect extends TokenEffectBase {
@Override
protected String getStackDescription(SpellAbility sa) {
@@ -57,21 +64,14 @@ public class AmassEffect extends SpellAbilityEffect {
useZoneTable = true;
}
+ MutableBoolean combatChanged = new MutableBoolean(false);
// create army token if needed
if (CardLists.count(activator.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Army")) == 0) {
final String tokenScript = "b_0_0_zombie_army";
- final Card prototype = TokenInfo.getProtoType(tokenScript, sa);
+ final Card prototype = TokenInfo.getProtoType(tokenScript, sa, false);
- for (final Card tok : TokenInfo.makeTokensFromPrototype(prototype, activator, 1, true)) {
-
- // Should this be catching the Card that's returned?
- Card c = game.getAction().moveToPlay(tok, sa);
- if (c.getZone() != null) {
- triggerList.put(ZoneType.None, c.getZone().getZoneType(), c);
- }
- c.updateStateForView();
- }
+ makeTokens(prototype, activator, sa, 1, true, false, triggerList, combatChanged);
if (!useZoneTable) {
triggerList.triggerChangesZoneAll(game);
@@ -80,12 +80,20 @@ public class AmassEffect extends SpellAbilityEffect {
game.fireEvent(new GameEventTokenCreated());
}
+ if (combatChanged.isTrue()) {
+ game.updateCombatForView();
+ game.fireEvent(new GameEventCombatChanged());
+ }
+ Map params = Maps.newHashMap();
+ params.put("CounterType", CounterType.get(CounterEnumType.P1P1));
+ params.put("Amount", 1);
+
CardCollectionView tgtCards = CardLists.getType(activator.getCardsIn(ZoneType.Battlefield), "Army");
- tgtCards = pc.chooseCardsForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblChooseAnArmy"), 1, 1, false);
+ tgtCards = pc.chooseCardsForEffect(tgtCards, sa, Localizer.getInstance().getMessage("lblChooseAnArmy"), 1, 1, false, params);
GameEntityCounterTable table = new GameEntityCounterTable();
for(final Card tgtCard : tgtCards) {
- tgtCard.addCounter(CounterType.P1P1, amount, activator, true, table);
+ tgtCard.addCounter(CounterEnumType.P1P1, amount, activator, true, table);
game.updateLastStateForCard(tgtCard);
if (remember) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java
index ff0c74c1020..cd8325cdba9 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java
@@ -35,7 +35,8 @@ public class AttachEffect extends SpellAbilityEffect {
sa.setHostCard(c);
}
- Card source = sa.getHostCard();
+ final Card source = sa.getHostCard();
+ final Game game = source.getGame();
CardCollection attachments;
final List targets = getDefinedOrTargeted(sa, "Defined");
@@ -57,15 +58,22 @@ public class AttachEffect extends SpellAbilityEffect {
final Player p = sa.getActivatingPlayer();
- if (sa.hasParam("Object")) {
- attachments = AbilityUtils.getDefinedCards(source, sa.getParam("Object"), sa);
- if (sa.hasParam("ChooseAnObject")) {
- Card c = p.getController().chooseSingleEntityForEffect(attachments, sa, sa.getParam("ChooseAnObject"));
- attachments.clear();
- if (c != null) {
- attachments.add(c);
- }
+ if (sa.hasParam("Choices")) {
+ ZoneType choiceZone = ZoneType.Battlefield;
+ if (sa.hasParam("ChoiceZone")) {
+ choiceZone = ZoneType.smartValueOf(sa.getParam("ChoiceZone"));
}
+ String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChoose") + " ";
+
+ CardCollection choices = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"), p, source, sa);
+
+ Card c = p.getController().chooseSingleEntityForEffect(choices, sa, title, null);
+ if (c == null) {
+ return;
+ }
+ attachments = new CardCollection(c);
+ } else if (sa.hasParam("Object")) {
+ attachments = AbilityUtils.getDefinedCards(source, sa.getParam("Object"), sa);
} else {
attachments = new CardCollection(source);
}
@@ -185,7 +193,8 @@ public class AttachEffect extends SpellAbilityEffect {
players.add(player);
}
}
- final Player pa = p.getController().chooseSingleEntityForEffect(players, aura, Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", CardTranslation.getTranslatedName(source.getName())));
+ final Player pa = p.getController().chooseSingleEntityForEffect(players, aura,
+ Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
if (pa != null) {
handleAura(source, pa);
return true;
@@ -198,7 +207,8 @@ public class AttachEffect extends SpellAbilityEffect {
return false;
}
- final Card o = p.getController().chooseSingleEntityForEffect(list, aura, Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(source.getName())));
+ final Card o = p.getController().chooseSingleEntityForEffect(list, aura,
+ Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(source.getName())), null);
if (o != null) {
handleAura(source, o);
//source.enchantEntity((Card) o);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java
index 311fd3ae2a0..83a0eaadad5 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java
@@ -1,6 +1,7 @@
package forge.game.ability.effects;
import forge.game.Game;
+import forge.game.ability.AbilityKey;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -8,11 +9,13 @@ import forge.game.card.CardLists;
import forge.game.card.CardZoneTable;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
+import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.collect.FCollectionView;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* TODO: Write javadoc for this type.
@@ -50,12 +53,24 @@ public class BalanceEffect extends SpellAbilityEffect {
continue;
}
if (zone.equals(ZoneType.Hand)) {
+ boolean firstDiscard = p.getNumDiscardedThisTurn() == 0;
+ final CardCollection discardedByPlayer = new CardCollection();
for (Card card : p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance)) {
if ( null == card ) continue;
- p.discard(card, sa, table);
+ if (p.discard(card, sa, table) != null) {
+ discardedByPlayer.add(card);
+ }
+ }
+
+ if (!discardedByPlayer.isEmpty()) {
+ final Map runParams = AbilityKey.newMap();
+ runParams.put(AbilityKey.Player, p);
+ runParams.put(AbilityKey.Cards, discardedByPlayer);
+ runParams.put(AbilityKey.Cause, sa);
+ runParams.put(AbilityKey.FirstTime, firstDiscard);
+ game.getTriggerHandler().runTrigger(TriggerType.DiscardedAll, runParams, false);
}
} else { // Battlefield
- // TODO: "can'e be sacrificed"
for(Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) {
if ( null == card ) continue;
game.getAction().sacrifice(card, sa, table);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/BondEffect.java b/forge-game/src/main/java/forge/game/ability/effects/BondEffect.java
index b41e6a265c5..4d9789bac9f 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/BondEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/BondEffect.java
@@ -31,7 +31,7 @@ public class BondEffect extends SpellAbilityEffect {
Card partner = cards.getFirst();
// skip choice if only one card on list
if (cards.size() > 1) {
- partner = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(cards, sa, Localizer.getInstance().getMessage("lblSelectACardPair"));
+ partner = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(cards, sa, Localizer.getInstance().getMessage("lblSelectACardPair"), null);
}
// pair choices together
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java
index 5d9d68b9a53..0b807bdd2bc 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java
@@ -18,7 +18,10 @@ import forge.util.CardTranslation;
import org.apache.commons.lang3.StringUtils;
+import com.google.common.collect.Maps;
+
import java.util.List;
+import java.util.Map;
public class ChangeCombatantsEffect extends SpellAbilityEffect {
@@ -45,8 +48,12 @@ public class ChangeCombatantsEffect extends SpellAbilityEffect {
final Combat combat = game.getCombat();
final GameEntity originalDefender = combat.getDefenderByAttacker(c);
final FCollectionView defs = combat.getDefenders();
- final GameEntity defender = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(defs, sa,
- Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName())), false);
+
+ String title = Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName()));
+ Map params = Maps.newHashMap();
+ params.put("Attacker", c);
+
+ final GameEntity defender = sa.getActivatingPlayer().getController().chooseSingleEntityForEffect(defs, sa, title, false, params);
if (originalDefender != null && !originalDefender.equals(defender)) {
AttackingBand ab = combat.getBandOfAttacker(c);
if (ab != null) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java
index c68a48b9836..8711a63ad8e 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java
@@ -507,7 +507,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachedTo"), tgtC.getController(), tgtC);
}
if (!list.isEmpty()) {
- Card attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", tgtC.toString()));
+ Map params = Maps.newHashMap();
+ params.put("Attach", tgtC);
+ Card attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", tgtC.toString()), params);
tgtC.attachToEntity(attachedTo);
} else { // When it should enter the battlefield attached to an illegal permanent it fails
continue;
@@ -517,7 +519,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("AttachedToPlayer")) {
FCollectionView list = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("AttachedToPlayer"), sa);
if (!list.isEmpty()) {
- Player attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", tgtC.toString()));
+ Map params = Maps.newHashMap();
+ params.put("Attach", tgtC);
+ Player attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, Localizer.getInstance().getMessage("lblSelectAPlayerAttachSourceTo", tgtC.toString()), params);
tgtC.attachToEntity(attachedTo);
}
else { // When it should enter the battlefield attached to an illegal player it fails
@@ -578,7 +582,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
} else {
- defender = player.getController().chooseSingleEntityForEffect(e, sa, Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(movedCard.getName())));
+ String title = Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(movedCard.getName()));
+ Map params = Maps.newHashMap();
+ params.put("Attacker", movedCard);
+ defender = player.getController().chooseSingleEntityForEffect(e, sa, title, params);
}
if (defender != null) {
combat.addAttacker(movedCard, defender);
@@ -1039,7 +1046,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (!list.isEmpty()) {
Card attachedTo = null;
if (list.size() > 1) {
- attachedTo = decider.getController().chooseSingleEntityForEffect(list, sa, Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName())));
+ String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName()));
+ Map params = Maps.newHashMap();
+ params.put("Attach", c);
+ attachedTo = decider.getController().chooseSingleEntityForEffect(list, sa, title, params);
}
else {
attachedTo = list.get(0);
@@ -1057,7 +1067,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sa.hasParam("AttachedToPlayer")) {
FCollectionView list = AbilityUtils.getDefinedPlayers(source, sa.getParam("AttachedToPlayer"), sa);
if (!list.isEmpty()) {
- Player attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName())));
+ String title = Localizer.getInstance().getMessage("lblSelectACardAttachSourceTo", CardTranslation.getTranslatedName(c.getName()));
+ Map params = Maps.newHashMap();
+ params.put("Attach", c);
+ Player attachedTo = player.getController().chooseSingleEntityForEffect(list, sa, title, params);
c.attachToEntity(attachedTo);
}
else { // When it should enter the battlefield attached to an illegal permanent it fails
@@ -1080,7 +1093,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
}
}
} else {
- defender = player.getController().chooseSingleEntityForEffect(e, sa, Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName())));
+ String title = Localizer.getInstance().getMessage("lblChooseDefenderToAttackWithCard", CardTranslation.getTranslatedName(c.getName()));
+ Map params = Maps.newHashMap();
+ params.put("Attacker", c);
+ defender = player.getController().chooseSingleEntityForEffect(e, sa, title, params);
}
if (defender != null) {
combat.addAttacker(c, defender);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java
index ee1cca795e7..faed7543bcc 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java
@@ -189,7 +189,7 @@ public class CharmEffect extends SpellAbilityEffect {
//String choosers = sa.getParam("Chooser");
FCollection opponents = activator.getOpponents(); // all cards have Choser$ Opponent, so it's hardcoded here
- chooser = activator.getController().chooseSingleEntityForEffect(opponents, sa, "Choose an opponent");
+ chooser = activator.getController().chooseSingleEntityForEffect(opponents, sa, "Choose an opponent", null);
source.setChosenPlayer(chooser);
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java
index 1a601c45a2a..3d55ced65bc 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java
@@ -84,7 +84,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
final CardCollectionView cl = CardLists.getType(land, type);
if (!cl.isEmpty()) {
final String prompt = Localizer.getInstance().getMessage("lblChoose") + " " + Lang.nounWithAmount(1, type);
- Card c = p.getController().chooseSingleEntityForEffect(cl, sa, prompt, false);
+ Card c = p.getController().chooseSingleEntityForEffect(cl, sa, prompt, false, null);
if (c != null) {
chosen.add(c);
}
@@ -100,7 +100,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
while (!creature.isEmpty()) {
Card c = p.getController().chooseSingleEntityForEffect(creature, sa,
Localizer.getInstance().getMessage("lblSelectCreatureWithTotalPowerLessOrEqualTo", (totP - chosenP - negativeNum))
- + "\r\n(" + Localizer.getInstance().getMessage("lblSelected") + ":" + chosenPool + ")\r\n(" + Localizer.getInstance().getMessage("lblTotalPowerNum", chosenP) + ")", chosenP <= totP);
+ + "\r\n(" + Localizer.getInstance().getMessage("lblSelected") + ":" + chosenPool + ")\r\n(" + Localizer.getInstance().getMessage("lblTotalPowerNum", chosenP) + ")", chosenP <= totP, null);
if (c == null) {
if (p.getController().confirmAction(sa, PlayerActionConfirmMode.OptionalChoose, Localizer.getInstance().getMessage("lblCancelChooseConfirm"))) {
break;
@@ -120,7 +120,7 @@ public class ChooseCardEffect extends SpellAbilityEffect {
Aggregates.random(choices, validAmount, chosen);
} else {
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseaCard") + " ";
- chosen.addAll(p.getController().chooseCardsForEffect(choices, sa, title, minAmount, validAmount, !sa.hasParam("Mandatory")));
+ chosen.addAll(p.getController().chooseCardsForEffect(choices, sa, title, minAmount, validAmount, !sa.hasParam("Mandatory"), null));
}
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseColorEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseColorEffect.java
index 86c01b45ada..c308e29ee8a 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChooseColorEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseColorEffect.java
@@ -40,6 +40,11 @@ public class ChooseColorEffect extends SpellAbilityEffect {
String[] restrictedChoices = sa.getParam("Choices").split(",");
colorChoices = Arrays.asList(restrictedChoices);
}
+ if (sa.hasParam("Exclude")) {
+ for (String s : sa.getParam("Exclude").split(",")) {
+ colorChoices.remove(s);
+ }
+ }
final List tgtPlayers = getTargetPlayers(sa);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCompanionEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCompanionEffect.java
new file mode 100644
index 00000000000..1282dc98b89
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCompanionEffect.java
@@ -0,0 +1,12 @@
+package forge.game.ability.effects;
+
+import forge.game.ability.SpellAbilityEffect;
+import forge.game.spellability.SpellAbility;
+
+public class ChooseCompanionEffect extends SpellAbilityEffect {
+
+ @Override
+ public void resolve(SpellAbility sa) {
+ // This isn't a real effect. Just need it for AI choosing.
+ }
+}
\ No newline at end of file
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseEvenOddEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseEvenOddEffect.java
new file mode 100644
index 00000000000..6d7388ddfd4
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseEvenOddEffect.java
@@ -0,0 +1,43 @@
+package forge.game.ability.effects;
+
+import forge.game.EvenOdd;
+import forge.game.ability.SpellAbilityEffect;
+import forge.game.card.Card;
+import forge.game.player.Player;
+import forge.game.player.PlayerController.BinaryChoiceType;
+import forge.game.spellability.SpellAbility;
+import forge.util.Localizer;
+
+public class ChooseEvenOddEffect extends SpellAbilityEffect {
+
+ /* (non-Javadoc)
+ * @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
+ */
+ @Override
+ protected String getStackDescription(SpellAbility sa) {
+ final StringBuilder sb = new StringBuilder();
+
+ for (final Player p : getTargetPlayers(sa)) {
+ sb.append(p).append(" ");
+ }
+ sb.append("chooses even or odd.");
+
+ return sb.toString();
+ }
+
+ @Override
+ public void resolve(SpellAbility sa) {
+ final Card card = sa.getHostCard();
+
+ for (final Player p : getTargetPlayers(sa)) {
+ if ((!sa.usesTargeting()) || p.canBeTargetedBy(sa)) {
+ EvenOdd chosen = p.getController().chooseBinary(sa, "odd or even", BinaryChoiceType.OddsOrEvens) ? EvenOdd.Odd : EvenOdd.Even;
+ card.setChosenEvenOdd(chosen);
+ if (sa.hasParam("Notify")) {
+ p.getGame().getAction().nofityOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), chosen), p);
+ }
+ }
+ }
+ card.updateStateForView();
+ }
+}
\ No newline at end of file
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java
index c5a46346887..f5f59dfb4e5 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java
@@ -8,8 +8,7 @@ import forge.game.card.Card;
import forge.game.event.GameEventCardModeChosen;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
-import forge.util.MyRandom;
-import forge.util.Localizer;
+import forge.util.Aggregates;
import java.util.List;
@@ -33,6 +32,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
final List abilities = Lists.newArrayList(sa.getAdditionalAbilityList("Choices"));
final SpellAbility fallback = sa.getAdditionalAbility("FallbackAbility");
+ final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("ChoiceAmount", "1"), sa);
final List tgtPlayers = getDefinedPlayersOrTargeted(sa);
@@ -43,8 +43,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
for (SpellAbility saChoice : abilities) {
if (!saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer()) ) {
saToRemove.add(saChoice);
- } else if (saChoice.hasParam("UnlessCost") &&
- "Player.IsRemembered".equals(saChoice.getParam("Defined"))) {
+ } else if (saChoice.hasParam("UnlessCost")) {
String unlessCost = saChoice.getParam("UnlessCost");
// Sac a permanent in presence of Sigarda, Host of Herons
// TODO: generalize this by testing if the unless cost can be paid
@@ -65,32 +64,32 @@ public class ChooseGenericEffect extends SpellAbilityEffect {
continue;
}
- SpellAbility chosenSA = null;
+ List