diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java
index d6204530436..e98fd7b6a7e 100644
--- a/forge-ai/src/main/java/forge/ai/AiAttackController.java
+++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java
@@ -30,6 +30,7 @@ import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.combat.GlobalAttackRestrictions;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -201,8 +202,11 @@ public class AiAttackController {
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
return true;
}
- if (this.attackers.size() == 1 && attacker.hasKeyword("Exalted")
- && ComputerUtilCombat.predictDamageTo(opp, 1, attacker, true) > 0) {
+
+ // TODO check if that makes sense
+ int exalted = ai.countExaltedBonus();
+ if (this.attackers.size() == 1 && exalted > 0
+ && ComputerUtilCombat.predictDamageTo(opp, exalted, attacker, true) > 0) {
return true;
}
@@ -316,7 +320,7 @@ public class AiAttackController {
}
continue;
}
- if (c.hasKeyword("Vigilance")) {
+ if (c.hasKeyword(Keyword.VIGILANCE)) {
vigilantes.add(c);
notNeededAsBlockers.remove(c); // they will be re-added later
if (canBlockAnAttacker(c, opponentsAttackers, false)) {
@@ -358,7 +362,7 @@ public class AiAttackController {
// In addition, if the computer guesses it needs no blockers, make sure
// that
// it won't be surprised by Exalted
- final int humanExaltedBonus = countExaltedBonus(opp);
+ final int humanExaltedBonus = opp.countExaltedBonus();
if (humanExaltedBonus > 0) {
final boolean finestHour = opp.isCardInPlay("Finest Hour");
@@ -543,7 +547,7 @@ public class AiAttackController {
int trampleDamage = 0;
for (Card attacker : blockedAttackers) {
- if (attacker.hasKeyword("Trample")) {
+ if (attacker.hasKeyword(Keyword.TRAMPLE)) {
int damage = ComputerUtilCombat.getAttack(attacker);
for (Card blocker : this.blockers) {
if (CombatUtil.canBlock(attacker, blocker)) {
@@ -729,20 +733,15 @@ public class AiAttackController {
// Exalted
if (combat.getAttackers().isEmpty()) {
- boolean exalted = false;
- int exaltedCount = 0;
- for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
- if (c.getName().equals("Rafiq of the Many") || c.getName().equals("Battlegrace Angel")) {
- exalted = true;
- break;
- }
- if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) {
- exalted = true;
- break;
- }
- if (c.hasKeyword("Exalted")) {
- exaltedCount++;
- if (exaltedCount > 2) {
+ boolean exalted = ai.countExaltedBonus() > 2;
+
+ if (!exalted) {
+ for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
+ if (c.getName().equals("Rafiq of the Many") || c.getName().equals("Battlegrace Angel")) {
+ exalted = true;
+ break;
+ }
+ if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) {
exalted = true;
break;
}
@@ -1049,24 +1048,6 @@ public class AiAttackController {
}
} // getAttackers()
- /**
- *
- * countExaltedBonus.
- *
- *
- * @param player
- * a {@link forge.game.player.Player} object.
- * @return a int.
- */
- public final static int countExaltedBonus(final Player player) {
- int bonus = 0;
- for (Card c : player.getCardsIn(ZoneType.Battlefield)) {
- bonus += c.getAmountOfKeyword("Exalted");
- }
-
- return bonus;
- }
-
/**
*
* getAttack.
@@ -1079,7 +1060,7 @@ public class AiAttackController {
public final static int getAttack(final Card c) {
int n = c.getNetCombatDamage();
- if (c.hasKeyword("Double Strike")) {
+ if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
n *= 2;
}
@@ -1110,7 +1091,7 @@ public class AiAttackController {
// Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking?
if ((attacker.hasSVar("NonCombatPriority"))
- && (!attacker.hasKeyword("Vigilance"))) {
+ && (!attacker.hasKeyword(Keyword.VIGILANCE))) {
// For each level of priority, enemy has to have life as much as the creature's power
// so a priority of 4 means the creature will not attack unless it can defeat that player in 4 successful attacks.
// the lower the priroity, the less willing the AI is to use the creature for attacking.
@@ -1162,7 +1143,7 @@ public class AiAttackController {
&& CombatUtil.canBlock(attacker, defender)) {
numberOfPossibleBlockers += 1;
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
- && !(attacker.hasKeyword("Undying") && attacker.getCounters(CounterType.P1P1) == 0)) {
+ && !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.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
@@ -1178,14 +1159,11 @@ public class AiAttackController {
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) {
canKillAllDangerous = false;
} else {
- for (KeywordInterface inst : defender.getKeywords()) {
- String keyword = inst.getOriginal();
- if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) {
- canKillAllDangerous = false;
- break;
- // there is a creature that can survive an attack from this creature
- // and combat will have negative effects
- }
+ if (defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT)
+ || defender.hasKeyword(Keyword.LIFELINK)) {
+ canKillAllDangerous = false;
+ // there is a creature that can survive an attack from this creature
+ // and combat will have negative effects
}
// Check if maybe we are too reckless in adding this attacker
@@ -1209,7 +1187,7 @@ public class AiAttackController {
}
}
- if (!attacker.hasKeyword("Vigilance") && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
+ if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
canKillAllDangerous = false;
canBeKilled = true;
canBeKilledByOne = true;
@@ -1295,7 +1273,7 @@ public class AiAttackController {
// creature would leave the battlefield
// no pain in exerting it
shouldExert = true;
- } else if (c.hasKeyword("Vigilance")) {
+ } else if (c.hasKeyword(Keyword.VIGILANCE)) {
// Free exert - why not?
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 f5ee0467fb0..d674d866cec 100644
--- a/forge-ai/src/main/java/forge/ai/AiBlockController.java
+++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java
@@ -25,6 +25,7 @@ import forge.game.GameEntity;
import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
@@ -117,11 +118,11 @@ public class AiBlockController {
// If I don't have any planeswalkers then sorting doesn't really matter
if (defenders.size() == 1) {
- final CardCollection attackers = combat.getAttackersOf(defenders.get(0));
+ final CardCollection attackers = combat.getAttackersOf(defenders.get(0));
// Begin with the attackers that pose the biggest threat
ComputerUtilCard.sortByEvaluateCreature(attackers);
CardLists.sortByPowerDesc(attackers);
- //move cards like Phage the Untouchable to the front
+ //move cards like Phage the Untouchable to the front
Collections.sort(attackers, new Comparator() {
@Override
public int compare(final Card o1, final Card o2) {
@@ -142,16 +143,16 @@ public class AiBlockController {
// defend planeswalkers with more loyalty before planeswalkers with less loyalty
// if planeswalker will be too difficult to defend don't even bother
for (GameEntity defender : defenders) {
- if (defender instanceof Card) {
- final CardCollection attackers = combat.getAttackersOf(defender);
- // Begin with the attackers that pose the biggest threat
- CardLists.sortByPowerDesc(attackers);
- for (final Card c : attackers) {
- sortedAttackers.add(c);
- }
- } else if (defender instanceof Player && defender.equals(ai)){
- firstAttacker = combat.getAttackersOf(defender);
- }
+ if (defender instanceof Card) {
+ final CardCollection attackers = combat.getAttackersOf(defender);
+ // Begin with the attackers that pose the biggest threat
+ CardLists.sortByPowerDesc(attackers);
+ for (final Card c : attackers) {
+ sortedAttackers.add(c);
+ }
+ } else if (defender instanceof Player && defender.equals(ai)) {
+ firstAttacker = combat.getAttackersOf(defender);
+ }
}
if (bLifeInDanger) {
@@ -180,7 +181,7 @@ public class AiBlockController {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
- || attacker.hasKeyword("Menace")) {
+ || attacker.hasKeyword(Keyword.MENACE)) {
continue;
}
@@ -204,11 +205,11 @@ public class AiBlockController {
&& !ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
// check whether it's better to block a creature without trample to absorb more damage
- if (attacker.hasKeyword("Trample")) {
+ if (attacker.hasKeyword(Keyword.TRAMPLE)) {
boolean doNotBlock = false;
for (Card other : attackersLeft) {
if (other.equals(attacker) || !CombatUtil.canBlock(other, blocker)
- || other.hasKeyword("Trample")
+ || other.hasKeyword(Keyword.TRAMPLE)
|| ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
|| ComputerUtilCombat.canDestroyBlocker(ai, blocker, other, combat, false)
|| other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
@@ -231,11 +232,10 @@ 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("Undying") && b.getCounters(CounterType.P1P1) == 0)
- || b.hasSVar("SacMe")
- || (b.hasStartOfKeyword("Vanishing") && b.getCounters(CounterType.TIME) == 1)
- || (b.hasStartOfKeyword("Fading") && b.getCounters(CounterType.FADE) == 0)
- || b.hasSVar("EndOfTurnLeavePlay")) {
+ 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)
+ || b.hasSVar("EndOfTurnLeavePlay")) {
blocker = b;
break;
}
@@ -245,7 +245,7 @@ public class AiBlockController {
if (b.hasSVar("SacMe") && Integer.parseInt(b.getSVar("SacMe")) > 3) {
blocker = b;
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
- blockedButUnkilled.add(attacker);
+ blockedButUnkilled.add(attacker);
}
break;
}
@@ -293,8 +293,7 @@ public class AiBlockController {
// 6. Blockers that don't survive until the next turn anyway
for (final Card attacker : attackersLeft) {
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
- || attacker.hasKeyword("Menace")
+ if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
}
@@ -304,12 +303,12 @@ public class AiBlockController {
final List blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
for (Card b : blockers) {
- if ((b.hasStartOfKeyword("Vanishing") && b.getCounters(CounterType.TIME) == 1)
- || (b.hasStartOfKeyword("Fading") && b.getCounters(CounterType.FADE) == 0)
- || b.hasSVar("EndOfTurnLeavePlay")) {
+ if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
+ || (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
+ || b.hasSVar("EndOfTurnLeavePlay")) {
blocker = b;
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
- blockedButUnkilled.add(attacker);
+ blockedButUnkilled.add(attacker);
}
break;
}
@@ -531,7 +530,7 @@ public class AiBlockController {
// Try to block a Menace attacker with two blockers, neither of which will die
for (final Card attacker : attackersLeft) {
- if (!attacker.hasKeyword("Menace") && !attacker.hasStartOfKeyword("CantBeBlockedByAmount LT2")) {
+ if (!attacker.hasKeyword(Keyword.MENACE) && !attacker.hasStartOfKeyword("CantBeBlockedByAmount LT2")) {
continue;
}
@@ -590,7 +589,7 @@ public class AiBlockController {
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
- || attacker.hasKeyword("Menace")
+ || attacker.hasKeyword(Keyword.MENACE)
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
}
@@ -645,7 +644,7 @@ public class AiBlockController {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
- || attacker.hasKeyword("Menace")
+ || attacker.hasKeyword(Keyword.MENACE)
|| ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
attackers.remove(0);
makeChumpBlocks(combat, attackers);
@@ -657,7 +656,7 @@ public class AiBlockController {
final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers);
// check if it's better to block a creature with lower power and without trample
- if (attacker.hasKeyword("Trample")) {
+ if (attacker.hasKeyword(Keyword.TRAMPLE)) {
final int damageAbsorbed = blocker.getLethalDamage();
if (attacker.getNetCombatDamage() > damageAbsorbed) {
for (Card other : attackers) {
@@ -665,7 +664,7 @@ public class AiBlockController {
continue;
}
if (other.getNetCombatDamage() >= damageAbsorbed
- && !other.hasKeyword("Trample")
+ && !other.hasKeyword(Keyword.TRAMPLE)
&& !other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
&& !ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
&& CombatUtil.canBlock(other, blocker, combat)) {
@@ -696,7 +695,7 @@ public class AiBlockController {
for (final Card attacker : currentAttackers) {
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
- && !attacker.hasKeyword("Menace")
+ && !attacker.hasKeyword(Keyword.MENACE)
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
}
@@ -729,7 +728,7 @@ public class AiBlockController {
List chumpBlockers;
- List tramplingAttackers = CardLists.getKeyword(attackers, "Trample");
+ List tramplingAttackers = CardLists.getKeyword(attackers, Keyword.TRAMPLE);
tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock));
// TODO - should check here for a "rampage-like" trigger that replaced
@@ -738,7 +737,7 @@ public class AiBlockController {
for (final Card attacker : tramplingAttackers) {
- if (((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("Menace")) && !combat.isBlocked(attacker))
+ if (((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) && !combat.isBlocked(attacker))
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
@@ -775,29 +774,29 @@ public class AiBlockController {
// Try to use safe blockers first
if (blockers.size() > 0) {
safeBlockers = getSafeBlockers(combat, attacker, blockers);
- for (final Card blocker : safeBlockers) {
- final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
- + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
- // Add an additional blocker if the current blockers are not
- // enough and the new one would deal additional damage
- if (damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))
- && ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0
- && CombatUtil.canBlock(attacker, blocker, combat)) {
- combat.addBlocker(attacker, blocker);
- }
- blockers.remove(blocker); // Don't check them again next
- }
+ for (final Card blocker : safeBlockers) {
+ final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
+ // Add an additional blocker if the current blockers are not
+ // enough and the new one would deal additional damage
+ if (damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))
+ && ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0
+ && CombatUtil.canBlock(attacker, blocker, combat)) {
+ combat.addBlocker(attacker, blocker);
+ }
+ blockers.remove(blocker); // Don't check them again next
+ }
}
// don't try to kill what can't be killed
- if (attacker.hasKeyword("indestructible") || ComputerUtil.canRegenerate(ai, attacker)) {
- continue;
+ if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(ai, attacker)) {
+ continue;
}
// Try to add blockers that could be destroyed, but are worth less than the attacker
// Don't use blockers without First Strike or Double Strike if attacker has it
if (ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false, combat)) {
- safeBlockers = CardLists.getKeyword(blockers, "First Strike");
- safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike"));
+ safeBlockers = CardLists.getKeyword(blockers, Keyword.FIRST_STRIKE);
+ safeBlockers.addAll(CardLists.getKeyword(blockers, Keyword.DOUBLE_STRIKE));
} else {
safeBlockers = new ArrayList<>(blockers);
}
@@ -870,7 +869,7 @@ public class AiBlockController {
for (final Card attacker : attackers) {
GameEntity def = combat.getDefenderByAttacker(attacker);
if (def instanceof Card && threatenedPWs.contains((Card) def)) {
- if (attacker.hasKeyword("Trample")) {
+ if (attacker.hasKeyword(Keyword.TRAMPLE)) {
// don't bother trying to chump a trampling creature
continue;
}
@@ -985,7 +984,7 @@ public class AiBlockController {
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) {
- diff = 0;
+ diff = 0;
}
// remove all attackers that can't be blocked anyway
@@ -1015,73 +1014,80 @@ public class AiBlockController {
// When the AI holds some Fog effect, don't bother about lifeInDanger
if (!ComputerUtil.hasAFogEffect(ai)) {
- lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
+ lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
makeTradeBlocks(combat); // choose necessary trade blocks
- // if life is still in danger
- if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
- makeChumpBlocks(combat); // choose necessary chump blocks
- } else {
- lifeInDanger = false;
+ // if life is still in danger
+ if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
+ makeChumpBlocks(combat); // choose necessary chump blocks
+ } else {
+ lifeInDanger = false;
}
- // if life is still in danger
- // Reinforce blockers blocking attackers with trample if life is still
- // in danger
- if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
- reinforceBlockersAgainstTrample(combat);
- } else {
- lifeInDanger = false;
+ // if life is still in danger
+ // Reinforce blockers blocking attackers with trample if life is
+ // still
+ // in danger
+ if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
+ reinforceBlockersAgainstTrample(combat);
+ } else {
+ lifeInDanger = false;
+ }
+ // Support blockers not destroying the attacker with more blockers
+ // to
+ // try to kill the attacker
+ if (!lifeInDanger) {
+ reinforceBlockersToKill(combat);
}
- // Support blockers not destroying the attacker with more blockers to
- // try to kill the attacker
- if (!lifeInDanger) {
- reinforceBlockersToKill(combat);
- }
- // == 2. If the AI life would still be in danger make a safer approach ==
- if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
- clearBlockers(combat, possibleBlockers); // reset every block assignment
- makeTradeBlocks(combat); // choose necessary trade blocks
- // if life is in danger
- makeGoodBlocks(combat);
- // choose necessary chump blocks if life is still in danger
- if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
- makeChumpBlocks(combat);
- } else {
- lifeInDanger = false;
- }
- // Reinforce blockers blocking attackers with trample if life is
- // still in danger
- if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
- reinforceBlockersAgainstTrample(combat);
- } else {
- lifeInDanger = false;
- }
- makeGangBlocks(combat);
- reinforceBlockersToKill(combat);
- }
+ // == 2. If the AI life would still be in danger make a safer
+ // approach ==
+ if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
+ clearBlockers(combat, possibleBlockers); // reset every block
+ // assignment
+ makeTradeBlocks(combat); // choose necessary trade blocks
+ // if life is in danger
+ makeGoodBlocks(combat);
+ // choose necessary chump blocks if life is still in danger
+ if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
+ makeChumpBlocks(combat);
+ } else {
+ lifeInDanger = false;
+ }
+ // Reinforce blockers blocking attackers with trample if life is
+ // still in danger
+ if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
+ reinforceBlockersAgainstTrample(combat);
+ } else {
+ lifeInDanger = false;
+ }
+ makeGangBlocks(combat);
+ reinforceBlockersToKill(combat);
+ }
- // == 3. If the AI life would be in serious danger make an even safer approach ==
- if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
- clearBlockers(combat, possibleBlockers); // reset every block assignment
- makeChumpBlocks(combat); // choose chump blocks
- if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
- makeTradeBlocks(combat); // choose necessary trade
- }
+ // == 3. If the AI life would be in serious danger make an even
+ // safer approach ==
+ if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
+ clearBlockers(combat, possibleBlockers); // reset every block
+ // assignment
+ makeChumpBlocks(combat); // choose chump blocks
+ if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
+ makeTradeBlocks(combat); // choose necessary trade
+ }
- if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
- makeGoodBlocks(combat);
- }
- // Reinforce blockers blocking attackers with trample if life is
- // still in danger
- else {
- reinforceBlockersAgainstTrample(combat);
- }
- makeGangBlocks(combat);
- // Support blockers not destroying the attacker with more blockers
- // to try to kill the attacker
- reinforceBlockersToKill(combat);
- }
+ if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
+ makeGoodBlocks(combat);
+ }
+ // Reinforce blockers blocking attackers with trample if life is
+ // still in danger
+ else {
+ reinforceBlockersAgainstTrample(combat);
+ }
+ makeGangBlocks(combat);
+ // Support blockers not destroying the attacker with more
+ // blockers
+ // to try to kill the attacker
+ reinforceBlockersToKill(combat);
+ }
}
// assign blockers that have to block
@@ -1167,10 +1173,11 @@ public class AiBlockController {
* Orders a blocker that put onto the battlefield blocking. Depends heavily
* on the implementation of orderBlockers().
*/
- public static CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) {
- // add blocker to existing ordering
- // sort by evaluate, then insert it appropriately
- // relies on current implementation of orderBlockers()
+ public static CardCollection orderBlocker(final Card attacker, final Card blocker,
+ final CardCollection oldBlockers) {
+ // add blocker to existing ordering
+ // sort by evaluate, then insert it appropriately
+ // relies on current implementation of orderBlockers()
final CardCollection allBlockers = new CardCollection(oldBlockers);
allBlockers.add(blocker);
ComputerUtilCard.sortByEvaluateCreature(allBlockers);
@@ -1182,24 +1189,28 @@ public class AiBlockController {
boolean newBlockerIsAdded = false;
// The new blocker comes right after this one
final Card newBlockerRightAfter = (newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1));
- if (newBlockerRightAfter == null && damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
- result.add(blocker);
- newBlockerIsAdded = true;
+ if (newBlockerRightAfter == null
+ && damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
+ result.add(blocker);
+ newBlockerIsAdded = true;
}
- // Don't bother to keep damage up-to-date after the new blocker is added, as we can't modify the order of the other cards anyway
+ // Don't bother to keep damage up-to-date after the new blocker is
+ // added, as we can't modify the order of the other cards anyway
for (final Card c : oldBlockers) {
- final int lethal = ComputerUtilCombat.getEnoughDamageToKill(c, damage, attacker, true);
- damage -= lethal;
- result.add(c);
- if (!newBlockerIsAdded && c == newBlockerRightAfter && damage <= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
- // If blocker is right after this card in priority and we have sufficient damage to kill it, add it here
- result.add(blocker);
- newBlockerIsAdded = true;
- }
+ final int lethal = ComputerUtilCombat.getEnoughDamageToKill(c, damage, attacker, true);
+ damage -= lethal;
+ result.add(c);
+ if (!newBlockerIsAdded && c == newBlockerRightAfter
+ && damage <= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
+ // If blocker is right after this card in priority and we have
+ // sufficient damage to kill it, add it here
+ result.add(blocker);
+ newBlockerIsAdded = true;
+ }
}
// We don't have sufficient damage, just add it at the end!
if (!newBlockerIsAdded) {
- result.add(blocker);
+ result.add(blocker);
}
return result;
diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java
index a645ec1a81c..3eed6899f30 100644
--- a/forge-ai/src/main/java/forge/ai/AiController.java
+++ b/forge-ai/src/main/java/forge/ai/AiController.java
@@ -42,6 +42,7 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.*;
+import forge.game.keyword.Keyword;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -159,15 +160,15 @@ public class AiController {
if ("NonActive".equals(curse) && !player.equals(game.getPhaseHandler().getPlayerTurn())) {
return true;
} else if ("DestroyCreature".equals(curse) && sa.isSpell() && host.isCreature()
- && !sa.getHostCard().hasKeyword("Indestructible")) {
+ && !sa.getHostCard().hasKeyword(Keyword.INDESTRUCTIBLE)) {
return true;
} else if ("CounterEnchantment".equals(curse) && sa.isSpell() && host.isEnchantment()
- && !sa.getHostCard().hasKeyword("CARDNAME can't be countered.")) {
+ && CardFactoryUtil.isCounterable(host)) {
return true;
- } else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && !host.hasKeyword("CARDNAME can't be countered.")
+ } else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
&& host.getCMC() == c.getCounters(CounterType.CHARGE)) {
return true;
- } else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && !host.hasKeyword("CARDNAME can't be countered.")) {
+ } else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
if (!card.isToken() && card.getName().equals(host.getName())) {
return true;
@@ -793,7 +794,7 @@ public class AiController {
}
}
// if the profile specifies it, deprioritize Storm spells in an attempt to build up storm count
- if (source.hasKeyword("Storm") && ai.getController() instanceof PlayerControllerAi) {
+ if (source.hasKeyword(Keyword.STORM) && ai.getController() instanceof PlayerControllerAi) {
p -= (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.PRIORITY_REDUCTION_FOR_STORM_SPELLS));
}
}
@@ -1335,7 +1336,7 @@ public class AiController {
continue;
}
- if (sa.getHostCard().hasKeyword("Storm")
+ if (sa.getHostCard().hasKeyword(Keyword.STORM)
&& sa.getApi() != ApiType.Counter // AI would suck at trying to deliberately proc a Storm counterspell
&& CardLists.filter(player.getCardsIn(ZoneType.Hand), Predicates.not(Predicates.or(CardPredicates.Presets.LANDS, CardPredicates.hasKeyword("Storm")))).size() > 0) {
if (game.getView().getStormCount() < this.getIntProperty(AiProps.MIN_COUNT_FOR_STORM_SPELLS)) {
@@ -1563,11 +1564,11 @@ public class AiController {
// and exaclty one counter of the specifice type gets high priority to keep the card
if (allies.contains(crd.getController())) {
// except if its a Chronozoa, because it WANTS to be removed to make more
- if (crd.hasKeyword("Vanishing") && !"Chronozoa".equals(crd.getName())) {
+ if (crd.hasKeyword(Keyword.VANISHING) && !"Chronozoa".equals(crd.getName())) {
if (crd.getCounters(CounterType.TIME) == 1) {
return CounterType.TIME;
}
- } else if (crd.hasKeyword("Fading")) {
+ } else if (crd.hasKeyword(Keyword.FADING)) {
if (crd.getCounters(CounterType.FADE) == 1) {
return CounterType.FADE;
}
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
index faed8a063a9..cc829bbeb43 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
@@ -37,6 +37,7 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.*;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -789,7 +790,7 @@ public class ComputerUtil {
}
if (destroy) {
- final CardCollection indestructibles = CardLists.getKeyword(remaining, "Indestructible");
+ final CardCollection indestructibles = CardLists.getKeyword(remaining, Keyword.INDESTRUCTIBLE);
if (!indestructibles.isEmpty()) {
return indestructibles.get(0);
}
@@ -826,7 +827,7 @@ public class ComputerUtil {
}
public static boolean canRegenerate(Player ai, final Card card) {
- if (card.hasKeyword("CARDNAME can't be regenerated.")) {
+ if (!card.canBeShielded()) {
return false;
}
@@ -939,7 +940,7 @@ public class ComputerUtil {
}
// try not to cast Raid creatures in main 1 if an attack is likely
- if ("Count$AttackersDeclared".equals(card.getSVar("RaidTest")) && !card.hasKeyword("Haste")) {
+ if ("Count$AttackersDeclared".equals(card.getSVar("RaidTest")) && !card.hasKeyword(Keyword.HASTE)) {
for (Card potentialAtkr: ai.getCreaturesInPlay()) {
if (ComputerUtilCard.doesCreatureAttackAI(ai, potentialAtkr)) {
return false;
@@ -951,12 +952,12 @@ public class ComputerUtil {
return true;
}
- if (card.isCreature() && !card.hasKeyword("Defender")
- && (card.hasKeyword("Haste") || ComputerUtil.hasACardGivingHaste(ai, true) || sa.isDash())) {
+ if (card.isCreature() && !card.hasKeyword(Keyword.DEFENDER)
+ && (card.hasKeyword(Keyword.HASTE) || ComputerUtil.hasACardGivingHaste(ai, true) || sa.isDash())) {
return true;
}
- if (card.hasKeyword("Exalted")) {
+ if (card.hasKeyword(Keyword.EXALTED)) {
return true;
}
@@ -991,16 +992,16 @@ public class ComputerUtil {
return true;
}
if (card.isCreature()) {
- if (buffedcard.hasKeyword("Soulbond") && !buffedcard.isPaired()) {
+ if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
return true;
}
- if (buffedcard.hasKeyword("Evolve")) {
+ if (buffedcard.hasKeyword(Keyword.EVOLVE)) {
if (buffedcard.getNetPower() < card.getNetPower() || buffedcard.getNetToughness() < card.getNetToughness()) {
return true;
}
}
}
- if (card.hasKeyword("Soulbond") && buffedcard.isCreature() && !buffedcard.isPaired()) {
+ if (card.hasKeyword(Keyword.SOULBOND) && buffedcard.isCreature() && !buffedcard.isPaired()) {
return true;
}
@@ -1248,7 +1249,7 @@ public class ComputerUtil {
// Special for Odric
if (ai.isCardInPlay("Odric, Lunarch Marshal")
- && !CardLists.getKeyword(all, "Haste").isEmpty()) {
+ && !CardLists.getKeyword(all, Keyword.HASTE).isEmpty()) {
return true;
}
@@ -1317,7 +1318,7 @@ public class ComputerUtil {
if ("Continuous".equals(params.get("Mode")) && params.containsKey("AddKeyword")
&& params.get("AddKeyword").contains("Haste")) {
- final ArrayList affected = Lists.newArrayList(params.get("Affected").split(","));
+ final ArrayList affected = Lists.newArrayList(params.get("Affected").split(","));
if (affected.contains("Creature")) {
return true;
}
@@ -1547,7 +1548,7 @@ public class ComputerUtil {
final Card c = (Card) o;
// indestructible
- if (c.hasKeyword("Indestructible")) {
+ if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
continue;
}
@@ -1594,7 +1595,7 @@ public class ComputerUtil {
} else if (o instanceof Player) {
final Player p = (Player) o;
- if (source.hasKeyword("Infect")) {
+ if (source.hasKeyword(Keyword.INFECT)) {
if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getPoisonCounters()) {
threatened.add(p);
}
@@ -1615,14 +1616,14 @@ public class ComputerUtil {
if (o instanceof Card) {
final Card c = (Card) o;
final boolean canRemove = (c.getNetToughness() <= dmg)
- || (!c.hasKeyword("Indestructible") && c.getShieldCount() == 0 && (dmg >= ComputerUtilCombat.getDamageToKill(c)));
+ || (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && (dmg >= ComputerUtilCombat.getDamageToKill(c)));
if (!canRemove) {
continue;
}
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) {
final boolean cantSave = c.getNetToughness() + toughness <= dmg
- || (!c.hasKeyword("Indestructible") && c.getShieldCount() == 0 && !grantIndestructible
+ || (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && !grantIndestructible
&& (dmg >= toughness + ComputerUtilCombat.getDamageToKill(c)));
if (cantSave && (tgt == null || !grantShroud)) {
continue;
@@ -1662,7 +1663,7 @@ public class ComputerUtil {
if (o instanceof Card) {
final Card c = (Card) o;
// indestructible
- if (c.hasKeyword("Indestructible")) {
+ if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
continue;
}
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
index 7b3975e8d8d..1e05d7a55e4 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
@@ -1081,7 +1081,7 @@ public class ComputerUtilCard {
valueTempo *= 2; //deal with annoying things
}
if (!destination.equals(ZoneType.Graveyard) && //TODO:boat-load of "when blah dies" triggers
- c.hasKeyword("Persist") || c.hasKeyword("Undying") || c.hasKeyword("Modular")) {
+ c.hasKeyword(Keyword.PERSIST) || c.hasKeyword(Keyword.UNDYING) || c.hasKeyword(Keyword.MODULAR)) {
valueTempo *= 2;
}
if (destination.equals(ZoneType.Hand) && !c.isToken()) {
@@ -1358,8 +1358,9 @@ public class ComputerUtilCard {
//1. save combatant
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && !pumpedWillDie
- && !c.hasKeyword("Indestructible")) { // hack because attackerWouldBeDestroyed() does not
- // check for Indestructible when computing lethal damage
+ && !c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
+ // hack because attackerWouldBeDestroyed()
+ // does not check for Indestructible when computing lethal damage
return true;
}
@@ -1396,17 +1397,17 @@ public class ComputerUtilCard {
int poisonPumped = opp.canReceiveCounters(CounterType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(pumped, ai) : 0;
// predict Infect
- if (pumpedDmg == 0 && c.hasKeyword("Infect")) {
+ if (pumpedDmg == 0 && c.hasKeyword(Keyword.INFECT)) {
if (poisonPumped > poisonOrig) {
pumpedDmg = poisonPumped;
}
}
if (combat.isBlocked(c)) {
- if (!c.hasKeyword("Trample")) {
+ if (!c.hasKeyword(Keyword.TRAMPLE)) {
dmg = 0;
}
- if (c.hasKeyword("Trample") || keywords.contains("Trample")) {
+ if (c.hasKeyword(Keyword.TRAMPLE) || keywords.contains("Trample")) {
for (Card b : combat.getBlockers(c)) {
pumpedDmg -= ComputerUtilCombat.getDamageToKill(b);
}
@@ -1415,8 +1416,8 @@ public class ComputerUtilCard {
}
}
if (pumpedDmg > dmg) {
- if ((!c.hasKeyword("Infect") && pumpedDmg >= opp.getLife())
- || (c.hasKeyword("Infect") && opp.canReceiveCounters(CounterType.POISON) && pumpedDmg >= opp.getPoisonCounters())) {
+ if ((!c.hasKeyword(Keyword.INFECT) && pumpedDmg >= opp.getLife())
+ || (c.hasKeyword(Keyword.INFECT) && opp.canReceiveCounters(CounterType.POISON) && pumpedDmg >= opp.getPoisonCounters())) {
return true;
}
}
@@ -1425,7 +1426,7 @@ public class ComputerUtilCard {
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && pumpedDmg > dmg) {
int totalPowerUnblocked = 0;
for (Card atk : combat.getAttackers()) {
- if (combat.isBlocked(atk) && !atk.hasKeyword("Trample")) {
+ if (combat.isBlocked(atk) && !atk.hasKeyword(Keyword.TRAMPLE)) {
continue;
}
if (atk == c) {
@@ -1458,7 +1459,7 @@ public class ComputerUtilCard {
}
//4. lifelink
- if (ai.canGainLife() && ai.getLife() > 0 && !c.hasKeyword("Lifelink") && keywords.contains("Lifelink")
+ if (ai.canGainLife() && ai.getLife() > 0 && !c.hasKeyword(Keyword.LIFELINK) && keywords.contains("Lifelink")
&& (combat.isAttacking(c) || combat.isBlocking(c))) {
int dmg = pumped.getNetCombatDamage();
//The actual dmg inflicted should be the sum of ComputerUtilCombat.predictDamageTo() for opposing creature
@@ -1471,7 +1472,7 @@ public class ComputerUtilCard {
List blockedBy = combat.getAttackersBlockedBy(c);
boolean attackerHasTrample = false;
for (Card b : blockedBy) {
- attackerHasTrample |= b.hasKeyword("Trample");
+ attackerHasTrample |= b.hasKeyword(Keyword.TRAMPLE);
}
if (attackerHasTrample && (sa.isAbility() || ComputerUtilCombat.lifeInDanger(ai, combat))) {
return true;
@@ -1713,10 +1714,10 @@ public class ComputerUtilCard {
}
public static boolean hasActiveUndyingOrPersist(final Card c) {
- if (c.hasKeyword("Undying") && c.getCounters(CounterType.P1P1) == 0) {
+ if (c.hasKeyword(Keyword.UNDYING) && c.getCounters(CounterType.P1P1) == 0) {
return true;
}
- if (c.hasKeyword("Persist") && c.getCounters(CounterType.M1M1) == 0) {
+ if (c.hasKeyword(Keyword.PERSIST) && c.getCounters(CounterType.M1M1) == 0) {
return true;
}
return false;
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
index e41429127e6..8f0198af2c0 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
@@ -36,6 +36,7 @@ import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.CostPayment;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.phase.Untap;
import forge.game.player.Player;
@@ -201,10 +202,10 @@ public class ComputerUtilCombat {
}
damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities);
- if (!attacker.hasKeyword("Infect")) {
+ if (!attacker.hasKeyword(Keyword.INFECT)) {
sum = ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true);
- if (attacker.hasKeyword("Double Strike")) {
- sum += ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true);
+ if (attacker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
+ sum *= 2;
}
}
return sum;
@@ -226,14 +227,15 @@ public class ComputerUtilCombat {
int damage = attacker.getNetCombatDamage();
int poison = 0;
damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, null, false);
- if (attacker.hasKeyword("Infect")) {
- poison += ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true);
- if (attacker.hasKeyword("Double Strike")) {
- poison += ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true);
+ if (attacker.hasKeyword(Keyword.INFECT)) {
+ int pd = ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true);
+ poison += pd;
+ if (attacker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
+ poison += pd;
}
}
- if (attacker.hasKeyword("Poisonous") && (damage > 0)) {
- poison += attacker.getKeywordMagnitude("Poisonous");
+ if (attacker.hasKeyword(Keyword.POISONOUS) && (damage > 0)) {
+ poison += attacker.getKeywordMagnitude(Keyword.POISONOUS);
}
return poison;
}
@@ -301,9 +303,9 @@ public class ComputerUtilCombat {
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage "
+ "as though it weren't blocked.")) {
unblocked.add(attacker);
- } else if (attacker.hasKeyword("Trample")
+ } else if (attacker.hasKeyword(Keyword.TRAMPLE)
&& (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, blockers))) {
- if (!attacker.hasKeyword("Infect")) {
+ if (!attacker.hasKeyword(Keyword.INFECT)) {
damage += ComputerUtilCombat.getAttack(attacker) - ComputerUtilCombat.totalShieldDamage(attacker, blockers);
}
}
@@ -330,6 +332,11 @@ public class ComputerUtilCombat {
*/
public static int resultingPoison(final Player ai, final Combat combat) {
+ // ai can't get poision counters, so the value can't change
+ if (!ai.canReceiveCounters(CounterType.POISON)) {
+ return ai.getPoisonCounters();
+ }
+
int poison = 0;
final List attackers = combat.getAttackersOf(ai);
@@ -343,13 +350,13 @@ public class ComputerUtilCombat {
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage"
+ " as though it weren't blocked.")) {
unblocked.add(attacker);
- } else if (attacker.hasKeyword("Trample")
+ } else if (attacker.hasKeyword(Keyword.TRAMPLE)
&& (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, blockers))) {
- if (attacker.hasKeyword("Infect")) {
+ if (attacker.hasKeyword(Keyword.INFECT)) {
poison += ComputerUtilCombat.getAttack(attacker) - ComputerUtilCombat.totalShieldDamage(attacker, blockers);
}
- if (attacker.hasKeyword("Poisonous")) {
- poison += attacker.getKeywordMagnitude("Poisonous");
+ if (attacker.hasKeyword(Keyword.POISONOUS)) {
+ poison += attacker.getKeywordMagnitude(Keyword.POISONOUS);
}
}
}
@@ -576,7 +583,7 @@ public class ComputerUtilCombat {
int defenderDamage = predictDamageByBlockerWithoutDoubleStrike(attacker, defender);
- if (defender.hasKeyword("Double Strike")) {
+ if (defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
defenderDamage += predictDamageTo(attacker, defenderDamage, defender, true);
}
@@ -590,25 +597,26 @@ public class ComputerUtilCombat {
* @return
*/
private static int predictDamageByBlockerWithoutDoubleStrike(final Card attacker, final Card defender) {
- if (attacker.getName().equals("Sylvan Basilisk") && !defender.hasKeyword("Indestructible")) {
+ if (attacker.getName().equals("Sylvan Basilisk") && !defender.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return 0;
}
int flankingMagnitude = 0;
- if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) {
+ if (attacker.hasKeyword(Keyword.FLANKING) && !defender.hasKeyword(Keyword.FLANKING)) {
- flankingMagnitude = attacker.getAmountOfKeyword("Flanking");
+ flankingMagnitude = attacker.getAmountOfKeyword(Keyword.FLANKING);
if (flankingMagnitude >= defender.getNetToughness()) {
return 0;
}
if ((flankingMagnitude >= (defender.getNetToughness() - defender.getDamage()))
- && !defender.hasKeyword("Indestructible")) {
+ && !defender.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return 0;
}
} // flanking
- if (attacker.hasKeyword("Indestructible") && !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) {
+ if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE)
+ && !(defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT))) {
return 0;
}
@@ -667,21 +675,21 @@ public class ComputerUtilCombat {
}
int flankingMagnitude = 0;
- if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
+ if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
- flankingMagnitude = attacker.getAmountOfKeyword("Flanking");
+ flankingMagnitude = attacker.getAmountOfKeyword(Keyword.FLANKING);
if (flankingMagnitude >= blocker.getNetToughness()) {
return 0;
}
if ((flankingMagnitude >= (blocker.getNetToughness() - blocker.getDamage()))
- && !blocker.hasKeyword("Indestructible")) {
+ && !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return 0;
}
} // flanking
- final int defBushidoMagnitude = blocker.getKeywordMagnitude("Bushido");
+ final int defBushidoMagnitude = blocker.getKeywordMagnitude(Keyword.BUSHIDO);
final int defenderDefense = (blocker.getLethalDamage() - flankingMagnitude) + defBushidoMagnitude;
@@ -727,16 +735,16 @@ public class ComputerUtilCombat {
for (final Card defender : blockers) {
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, true)
- && !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) {
+ && !(defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT))) {
return true;
}
- if (defender.hasKeyword("First Strike") || defender.hasKeyword("Double Strike")) {
+ if (defender.hasKeyword(Keyword.FIRST_STRIKE) || defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
firstStrikeBlockerDmg += defender.getNetCombatDamage();
}
}
// Consider first strike and double strike
- if (attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) {
+ if (attacker.hasKeyword(Keyword.FIRST_STRIKE) || attacker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
return firstStrikeBlockerDmg >= ComputerUtilCombat.getDamageToKill(attacker);
}
@@ -920,9 +928,9 @@ public class ComputerUtilCombat {
// if the attacker has first strike and wither the blocker will deal
// less damage than expected
if (dealsFirstStrikeDamage(attacker, withoutAbilities, null)
- && (attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))
+ && (attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT))
&& !dealsFirstStrikeDamage(blocker, withoutAbilities, null)
- && !blocker.hasKeyword("CARDNAME can't have counters put on it.")) {
+ && !blocker.canReceiveCounters(CounterType.M1M1)) {
power -= attacker.getNetCombatDamage();
}
@@ -1069,8 +1077,8 @@ public class ComputerUtilCombat {
public static int predictToughnessBonusOfBlocker(final Card attacker, final Card blocker, boolean withoutAbilities) {
int toughness = 0;
- if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
- toughness -= attacker.getAmountOfKeyword("Flanking");
+ if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
+ toughness -= attacker.getAmountOfKeyword(Keyword.FLANKING);
}
if (blocker.getName().equals("Shape Stealer")) {
@@ -1239,9 +1247,7 @@ public class ComputerUtilCombat {
//check Exalted only for the first attacker
if (combat != null && combat.getAttackers().isEmpty()) {
- for (Card card : attacker.getController().getCardsIn(ZoneType.Battlefield)) {
- power += card.getAmountOfKeyword("Exalted");
- }
+ power += attacker.getController().countExaltedBonus();
}
// Serene Master switches power with attacker
@@ -1263,9 +1269,9 @@ public class ComputerUtilCombat {
// less damage than expected
if (null != blocker) {
if (ComputerUtilCombat.dealsFirstStrikeDamage(blocker, withoutAbilities, combat)
- && (blocker.hasKeyword("Wither") || blocker.hasKeyword("Infect"))
- && !ComputerUtilCombat.dealsFirstStrikeDamage(attacker, withoutAbilities, combat)
- && !attacker.hasKeyword("CARDNAME can't have counters put on it.")) {
+ && (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT))
+ && !ComputerUtilCombat.dealsFirstStrikeDamage(attacker, withoutAbilities, combat)
+ && !attacker.canReceiveCounters(CounterType.M1M1)) {
power -= blocker.getNetCombatDamage();
}
theTriggers.addAll(blocker.getTriggers());
@@ -1446,9 +1452,7 @@ public class ComputerUtilCombat {
//check Exalted only for the first attacker
if (combat != null && combat.getAttackers().isEmpty()) {
- for (Card card : attacker.getController().getCardsIn(ZoneType.Battlefield)) {
- toughness += card.getAmountOfKeyword("Exalted");
- }
+ toughness += attacker.getController().countExaltedBonus();
}
if (blocker != null && attacker.getName().equals("Shape Stealer")) {
@@ -1494,7 +1498,7 @@ public class ComputerUtilCombat {
} else if (params.containsKey("Affected") && params.get("Affected").contains("untapped")) {
final String valid = TextUtil.fastReplace(params.get("Affected"), "untapped", "Creature");
if (!attacker.isValid(valid, card.getController(), card, null)
- || attacker.hasKeyword("Vigilance")) {
+ || attacker.hasKeyword(Keyword.VIGILANCE)) {
continue;
}
// remove the bonus, because it will no longer be granted
@@ -1647,7 +1651,7 @@ public class ComputerUtilCombat {
if (blocker.isEquippedBy("Godsend")) {
return true;
}
- if (attacker.hasKeyword("Indestructible") || ComputerUtil.canRegenerate(attacker.getController(), attacker)) {
+ if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(attacker.getController(), attacker)) {
return false;
}
@@ -1712,12 +1716,12 @@ public class ComputerUtilCombat {
*/
public static boolean attackerCantBeDestroyedInCombat(Player ai, final Card attacker) {
// attacker is either indestructible or may regenerate
- if (attacker.hasKeyword("Indestructible") || (ComputerUtil.canRegenerate(ai, attacker))) {
+ if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, attacker))) {
return true;
}
// attacker will regenerate
- if (attacker.getShieldCount() > 0 && !attacker.hasKeyword("CARDNAME can't be regenerated.")) {
+ if (attacker.getShieldCount() > 0 && attacker.canBeShielded()) {
return true;
}
@@ -1766,24 +1770,24 @@ public class ComputerUtilCombat {
}
int flankingMagnitude = 0;
- if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
+ if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
- flankingMagnitude = attacker.getAmountOfKeyword("Flanking");
+ flankingMagnitude = attacker.getAmountOfKeyword(Keyword.FLANKING);
if (flankingMagnitude >= blocker.getNetToughness()) {
return false;
}
if ((flankingMagnitude >= (blocker.getNetToughness() - blocker.getDamage()))
- && !blocker.hasKeyword("Indestructible")) {
+ && !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return false;
}
} // flanking
- if (((attacker.hasKeyword("Indestructible") || (ComputerUtil.canRegenerate(ai, attacker) && !withoutAbilities))
- && !(blocker.hasKeyword("Wither") || blocker.hasKeyword("Infect")))
- || (attacker.hasKeyword("Persist") && !attacker.canReceiveCounters(CounterType.M1M1) && (attacker
+ if (((attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, attacker) && !withoutAbilities))
+ && !(blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)))
+ || (attacker.hasKeyword(Keyword.PERSIST) && !attacker.canReceiveCounters(CounterType.M1M1) && (attacker
.getCounters(CounterType.M1M1) == 0))
- || (attacker.hasKeyword("Undying") && !attacker.canReceiveCounters(CounterType.P1P1) && (attacker
+ || (attacker.hasKeyword(Keyword.UNDYING) && !attacker.canReceiveCounters(CounterType.P1P1) && (attacker
.getCounters(CounterType.P1P1) == 0))) {
return false;
}
@@ -1830,7 +1834,7 @@ public class ComputerUtilCombat {
final int attackerLife = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities, withoutAttackerStaticAbilities);
- if (blocker.hasKeyword("Double Strike")) {
+ if (blocker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
if (defenderDamage > 0 && (hasKeyword(blocker, "Deathtouch", withoutAbilities, combat) || attacker.hasSVar("DestroyWhenDamaged"))) {
return true;
}
@@ -1840,7 +1844,8 @@ public class ComputerUtilCombat {
// Attacker may kill the blocker before he can deal normal
// (secondary) damage
- if (dealsFirstStrikeDamage(attacker, withoutAbilities, combat) && !blocker.hasKeyword("Indestructible")) {
+ if (dealsFirstStrikeDamage(attacker, withoutAbilities, combat)
+ && !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
if (attackerDamage >= defenderLife) {
return false;
}
@@ -1856,7 +1861,7 @@ public class ComputerUtilCombat {
else { // no double strike for defender
// Attacker may kill the blocker before he can deal any damage
if (dealsFirstStrikeDamage(attacker, withoutAbilities, combat)
- && !blocker.hasKeyword("Indestructible")
+ && !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)
&& !dealsFirstStrikeDamage(blocker, withoutAbilities, combat)) {
if (attackerDamage >= defenderLife) {
@@ -1895,7 +1900,7 @@ public class ComputerUtilCombat {
for (Card attacker : attackers) {
if (ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, true)
- && !(attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))) {
+ && !(attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT))) {
return true;
}
}
@@ -1913,19 +1918,21 @@ public class ComputerUtilCombat {
}
int flankingMagnitude = 0;
- if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
+ if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
- flankingMagnitude = attacker.getAmountOfKeyword("Flanking");
+ flankingMagnitude = attacker.getAmountOfKeyword(Keyword.FLANKING);
if (flankingMagnitude >= blocker.getNetToughness()) {
return true;
}
- if ((flankingMagnitude >= ComputerUtilCombat.getDamageToKill(blocker)) && !blocker.hasKeyword("Indestructible")) {
+ if ((flankingMagnitude >= ComputerUtilCombat.getDamageToKill(blocker))
+ && !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return true;
}
} // flanking
- if (blocker.hasKeyword("Indestructible") || dontTestRegen || ComputerUtil.canRegenerate(blocker.getController(), blocker)) {
+ if (blocker.hasKeyword(Keyword.INDESTRUCTIBLE) || dontTestRegen
+ || ComputerUtil.canRegenerate(blocker.getController(), blocker)) {
return false;
}
@@ -2010,11 +2017,11 @@ public class ComputerUtilCombat {
return true;
}
- if (((blocker.hasKeyword("Indestructible") || (ComputerUtil.canRegenerate(ai, blocker) && !withoutAbilities)) && !(attacker
- .hasKeyword("Wither") || attacker.hasKeyword("Infect")))
- || (blocker.hasKeyword("Persist") && !blocker.canReceiveCounters(CounterType.M1M1) && (blocker
+ if (((blocker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, blocker) && !withoutAbilities)) && !(attacker
+ .hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT)))
+ || (blocker.hasKeyword(Keyword.PERSIST) && !blocker.canReceiveCounters(CounterType.M1M1) && (blocker
.getCounters(CounterType.M1M1) == 0))
- || (blocker.hasKeyword("Undying") && !blocker.canReceiveCounters(CounterType.P1P1) && (blocker
+ || (blocker.hasKeyword(Keyword.UNDYING) && !blocker.canReceiveCounters(CounterType.P1P1) && (blocker
.getCounters(CounterType.P1P1) == 0))) {
return false;
}
@@ -2064,7 +2071,7 @@ public class ComputerUtilCombat {
final int attackerLife = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities, withoutAttackerStaticAbilities);
- if (attacker.hasKeyword("Double Strike")) {
+ if (attacker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
if (attackerDamage > 0 && (hasKeyword(attacker, "Deathtouch", withoutAbilities, combat) || blocker.hasSVar("DestroyWhenDamaged"))) {
return true;
}
@@ -2074,7 +2081,8 @@ public class ComputerUtilCombat {
// Attacker may kill the blocker before he can deal normal
// (secondary) damage
- if (dealsFirstStrikeDamage(blocker, withoutAbilities, combat) && !attacker.hasKeyword("Indestructible")) {
+ if (dealsFirstStrikeDamage(blocker, withoutAbilities, combat)
+ && !attacker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
if (defenderDamage >= attackerLife) {
return false;
}
@@ -2089,7 +2097,8 @@ public class ComputerUtilCombat {
else { // no double strike for attacker
// Defender may kill the attacker before he can deal any damage
- if (dealsFirstStrikeDamage(blocker, withoutAbilities, combat) && !attacker.hasKeyword("Indestructible")
+ if (dealsFirstStrikeDamage(blocker, withoutAbilities, combat)
+ && !attacker.hasKeyword(Keyword.INDESTRUCTIBLE)
&& !dealsFirstStrikeDamage(attacker, withoutAbilities, combat)) {
if (defenderDamage >= attackerLife) {
@@ -2136,7 +2145,7 @@ public class ComputerUtilCombat {
return damageMap;
}
- final boolean hasTrample = attacker.hasKeyword("Trample");
+ final boolean hasTrample = attacker.hasKeyword(Keyword.TRAMPLE);
if (block.size() == 1) {
final Card blocker = block.getFirst();
@@ -2238,11 +2247,11 @@ public class ComputerUtilCombat {
final boolean noPrevention) {
final int killDamage = c.isPlaneswalker() ? c.getCurrentLoyalty() : ComputerUtilCombat.getDamageToKill(c);
- if (c.hasKeyword("Indestructible") || c.getShieldCount() > 0) {
- if (!(source.hasKeyword("Wither") || source.hasKeyword("Infect"))) {
+ if (c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getShieldCount() > 0) {
+ if (!(source.hasKeyword(Keyword.WITHER) || source.hasKeyword(Keyword.INFECT))) {
return maxDamage + 1;
}
- } else if (source.hasKeyword("Deathtouch")) {
+ } else if (source.hasKeyword(Keyword.DEATHTOUCH)) {
for (int i = 1; i <= maxDamage; i++) {
if (noPrevention) {
if (c.staticReplaceDamage(i, source, isCombat) > 0) {
@@ -2405,7 +2414,7 @@ public class ComputerUtilCombat {
public final static boolean dealsFirstStrikeDamage(final Card combatant, final boolean withoutAbilities, final Combat combat) {
- if (combatant.hasKeyword("Double Strike") || combatant.hasKeyword("First Strike")) {
+ if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
return true;
}
@@ -2516,7 +2525,7 @@ public class ComputerUtilCombat {
public static boolean attackerHasThreateningAfflict(Card attacker, Player aiDefender) {
// TODO: expand this to account for more complex situations like the Wildfire Eternal unblocked trigger
- int afflictDmg = attacker.getKeywordMagnitude("Afflict");
+ int afflictDmg = attacker.getKeywordMagnitude(Keyword.AFFLICT);
return afflictDmg > attacker.getNetPower() || afflictDmg >= aiDefender.getLife();
}
@@ -2541,18 +2550,10 @@ public class ComputerUtilCombat {
CardCollection withoutEvasion = new CardCollection();
for (Card atk : attackers) {
- boolean hasProtection = false;
- for (KeywordInterface inst : atk.getKeywords()) {
- String kw = inst.getOriginal();
- if (kw.startsWith("Protection")) {
- hasProtection = true;
- break;
- }
- }
-
- if (atk.hasKeyword("Flying") || atk.hasKeyword("Shadow")
- || atk.hasKeyword("Horsemanship") || (atk.hasKeyword("Fear")
- || atk.hasKeyword("Intimidate") || atk.hasKeyword("Skulk") || hasProtection)) {
+ if (atk.hasKeyword(Keyword.FLYING) || atk.hasKeyword(Keyword.SHADOW)
+ || atk.hasKeyword(Keyword.HORSEMANSHIP) || (atk.hasKeyword(Keyword.FEAR)
+ || atk.hasKeyword(Keyword.INTIMIDATE) || atk.hasKeyword(Keyword.SKULK)
+ || atk.hasKeyword(Keyword.PROTECTION))) {
withEvasion.add(atk);
} else {
withoutEvasion.add(atk);
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java
index 73e4f20c520..104e4843ff0 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java
@@ -9,6 +9,7 @@ import forge.game.GameActionUtil;
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.CardPredicates.Presets;
import forge.game.card.CounterType;
@@ -446,13 +447,13 @@ public class ComputerUtilCost {
// Check for stuff like Nether Void
int extraManaNeeded = 0;
if (sa instanceof Spell) {
- final boolean cannotBeCountered = sa.getHostCard().hasKeyword("CARDNAME can't be countered.");
+ final boolean cannotBeCountered = !CardFactoryUtil.isCounterable(sa.getHostCard());
for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) {
final String snem = c.getSVar("AI_SpellsNeedExtraMana");
if (!StringUtils.isBlank(snem)) {
- if (cannotBeCountered && c.getName().equals("Nether Void")) {
- continue;
- }
+ if (cannotBeCountered && c.getName().equals("Nether Void")) {
+ continue;
+ }
String[] parts = TextUtil.split(snem, ' ');
boolean meetsRestriction = parts.length == 1 || player.isValid(parts[1], c.getController(), c, sa);
if(!meetsRestriction)
@@ -467,7 +468,7 @@ public class ComputerUtilCost {
}
for (Card c : player.getCardsIn(ZoneType.Command)) {
if (cannotBeCountered) {
- continue;
+ continue;
}
final String snem = c.getSVar("SpellsNeedExtraManaEffect");
if (!StringUtils.isBlank(snem)) {
diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
index e9e2b7bf894..3b7a9c7890a 100644
--- a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
+++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
@@ -7,6 +7,7 @@ import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.cost.CostPayEnergy;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.spellability.SpellAbility;
@@ -53,10 +54,10 @@ public class CreatureEvaluator implements Function {
}
// Evasion keywords
- if (c.hasKeyword("Flying")) {
+ if (c.hasKeyword(Keyword.FLYING)) {
value += addValue(power * 10, "flying");
}
- if (c.hasKeyword("Horsemanship")) {
+ if (c.hasKeyword(Keyword.HORSEMANSHIP)) {
value += addValue(power * 10, "horses");
}
if (c.hasKeyword("Unblockable")) {
@@ -65,13 +66,13 @@ public class CreatureEvaluator implements Function {
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
value += addValue(power * 6, "thorns");
}
- if (c.hasKeyword("Fear")) {
+ if (c.hasKeyword(Keyword.FEAR)) {
value += addValue(power * 6, "fear");
}
- if (c.hasKeyword("Intimidate")) {
+ if (c.hasKeyword(Keyword.INTIMIDATE)) {
value += addValue(power * 6, "intimidate");
}
- if (c.hasStartOfKeyword("Menace")) {
+ if (c.hasKeyword(Keyword.MENACE)) {
value += addValue(power * 4, "menace");
}
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
@@ -81,49 +82,49 @@ public class CreatureEvaluator implements Function {
// Other good keywords
if (power > 0) {
- if (c.hasKeyword("Double Strike")) {
+ if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
value += addValue(10 + (power * 15), "ds");
- } else if (c.hasKeyword("First Strike")) {
+ } else if (c.hasKeyword(Keyword.FIRST_STRIKE)) {
value += addValue(10 + (power * 5), "fs");
}
- if (c.hasKeyword("Deathtouch")) {
+ if (c.hasKeyword(Keyword.DEATHTOUCH)) {
value += addValue(25, "dt");
}
- if (c.hasKeyword("Lifelink")) {
+ if (c.hasKeyword(Keyword.LIFELINK)) {
value += addValue(power * 10, "lifelink");
}
- if (power > 1 && c.hasKeyword("Trample")) {
+ if (power > 1 && c.hasKeyword(Keyword.TRAMPLE)) {
value += addValue((power - 1) * 5, "trample");
}
- if (c.hasKeyword("Vigilance")) {
+ if (c.hasKeyword(Keyword.VIGILANCE)) {
value += addValue((power * 5) + (toughness * 5), "vigilance");
}
- if (c.hasKeyword("Wither")) {
+ if (c.hasKeyword(Keyword.WITHER)) {
value += addValue(power * 10, "Wither");
}
- if (c.hasKeyword("Infect")) {
+ if (c.hasKeyword(Keyword.INFECT)) {
value += addValue(power * 15, "infect");
}
- value += addValue(c.getKeywordMagnitude("Rampage"), "rampage");
- value += addValue(c.getKeywordMagnitude("Afflict") * 5, "afflict");
+ value += addValue(c.getKeywordMagnitude(Keyword.RAMPAGE), "rampage");
+ value += addValue(c.getKeywordMagnitude(Keyword.AFFLICT) * 5, "afflict");
}
- value += addValue(c.getKeywordMagnitude("Bushido") * 16, "bushido");
- value += addValue(c.getAmountOfKeyword("Flanking") * 15, "flanking");
- value += addValue(c.getAmountOfKeyword("Exalted") * 15, "exalted");
- value += addValue(c.getKeywordMagnitude("Annihilator") * 50, "eldrazi");
- value += addValue(c.getKeywordMagnitude("Absorb") * 11, "absorb");
+ value += addValue(c.getKeywordMagnitude(Keyword.BUSHIDO) * 16, "bushido");
+ value += addValue(c.getAmountOfKeyword(Keyword.FLANKING) * 15, "flanking");
+ value += addValue(c.getAmountOfKeyword(Keyword.EXALTED) * 15, "exalted");
+ value += addValue(c.getKeywordMagnitude(Keyword.ANNIHILATOR) * 50, "eldrazi");
+ value += addValue(c.getKeywordMagnitude(Keyword.ABSORB) * 11, "absorb");
// Keywords that may produce temporary or permanent buffs over time
- if (c.hasKeyword("Prowess")) {
+ if (c.hasKeyword(Keyword.PROWESS)) {
value += addValue(5, "prowess");
}
- if (c.hasKeyword("Outlast")) {
+ if (c.hasKeyword(Keyword.OUTLAST)) {
value += addValue(10, "outlast");
}
// Defensive Keywords
- if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
+ if (c.hasKeyword(Keyword.REACH) && !c.hasKeyword(Keyword.FLYING)) {
value += addValue(5, "reach");
}
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
@@ -131,7 +132,7 @@ public class CreatureEvaluator implements Function {
}
// Protection
- if (c.hasKeyword("Indestructible")) {
+ if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
value += addValue(70, "darksteel");
}
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
@@ -139,20 +140,17 @@ public class CreatureEvaluator implements Function {
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
value += addValue(50, "fogbank");
}
- if (c.hasKeyword("Hexproof")) {
+ if (c.hasKeyword(Keyword.HEXPROOF)) {
value += addValue(35, "hexproof");
- } else if (c.hasKeyword("Shroud")) {
+ } else if (c.hasKeyword(Keyword.SHROUD)) {
value += addValue(30, "shroud");
}
- if (c.hasStartOfKeyword("Protection")) {
+ if (c.hasKeyword(Keyword.PROTECTION)) {
value += addValue(20, "protection");
}
- if (c.hasStartOfKeyword("PreventAllDamageBy")) {
- value += addValue(10, "prevent-dmg");
- }
// Bad keywords
- if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
+ if (c.hasKeyword(Keyword.DEFENDER) || c.hasKeyword("CARDNAME can't attack.")) {
value -= subValue((power * 9) + 40, "defender");
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
value -= subValue(40, "sac-end");
@@ -188,17 +186,17 @@ public class CreatureEvaluator implements Function {
value -= subValue(30, "cupkeep");
} else if (c.hasStartOfKeyword("UpkeepCost")) {
value -= subValue(20, "sac-unless");
- } else if (c.hasStartOfKeyword("Echo") && c.cameUnderControlSinceLastUpkeep()) {
+ } else if (c.hasKeyword(Keyword.ECHO) && c.cameUnderControlSinceLastUpkeep()) {
value -= subValue(10, "echo-unpaid");
}
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
value -= subValue(20, "upkeep-dmg");
}
- if (c.hasStartOfKeyword("Fading")) {
+ if (c.hasKeyword(Keyword.FADING)) {
value -= subValue(20, "fading");
}
- if (c.hasStartOfKeyword("Vanishing")) {
+ if (c.hasKeyword(Keyword.VANISHING)) {
value -= subValue(20, "vanishing");
}
if (c.getSVar("Targeting").equals("Dies")) {
diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java
index d69ddd0c7c8..db77d699d3e 100644
--- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java
+++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java
@@ -33,6 +33,7 @@ import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.CostPart;
+import forge.game.keyword.Keyword;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
@@ -206,7 +207,9 @@ public class SpecialCardAi {
private static final int demonSacThreshold = Integer.MAX_VALUE; // if we're in dire conditions, sac everything from worst to best hoping to find an answer
public static boolean considerSacrificingCreature(final Player ai, final SpellAbility sa) {
- CardCollection flyingCreatures = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Predicates.and(CardPredicates.Presets.UNTAPPED, Predicates.or(CardPredicates.hasKeyword("Flying"), CardPredicates.hasKeyword("Reach"))));
+ CardCollection flyingCreatures = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield),
+ Predicates.and(CardPredicates.Presets.UNTAPPED, Predicates.or(
+ CardPredicates.hasKeyword(Keyword.FLYING), CardPredicates.hasKeyword(Keyword.REACH))));
boolean hasUsefulBlocker = false;
for (Card c : flyingCreatures) {
@@ -329,7 +332,7 @@ public class SpecialCardAi {
boolean oppHasFirstStrike = false;
boolean oppCantDie = true;
boolean unblocked = opposition.isEmpty();
- boolean canTrample = source.hasKeyword("Trample");
+ boolean canTrample = source.hasKeyword(Keyword.TRAMPLE);
if (!isBlocking && combat.getDefenderByAttacker(source) instanceof Card) {
int loyalty = ((Card)combat.getDefenderByAttacker(source)).getCounters(CounterType.LOYALTY);
@@ -351,7 +354,7 @@ public class SpecialCardAi {
}
for (Card c : opposition) {
- if (c.hasKeyword("First Strike") || c.hasKeyword("Double Strike")) {
+ if (c.hasKeyword(Keyword.FIRST_STRIKE) || c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
oppHasFirstStrike = true;
}
if (!ComputerUtilCombat.attackerCantBeDestroyedInCombat(c.getController(), c)) {
@@ -382,8 +385,8 @@ public class SpecialCardAi {
// Already enough to kill the blockers and survive, don't overpump
return false;
}
- if (oppCantDie && !source.hasKeyword("Trample") && !source.hasKeyword("Wither")
- && !source.hasKeyword("Infect") && predictedPT.getLeft() <= oppT) {
+ if (oppCantDie && !source.hasKeyword(Keyword.TRAMPLE) && !source.hasKeyword(Keyword.WITHER)
+ && !source.hasKeyword(Keyword.INFECT) && predictedPT.getLeft() <= oppT) {
// Can't kill or cripple anyone, as well as can't Trample over, so don't pump
return false;
}
@@ -400,7 +403,7 @@ public class SpecialCardAi {
CardCollection potentialBlockers = new CardCollection();
for (Card b : oppInPlay) {
- if (CombatUtil.canBlock(sa.getHostCard(), b)) {
+ if (CombatUtil.canBlock(source, b)) {
potentialBlockers.add(b);
}
}
@@ -408,7 +411,7 @@ public class SpecialCardAi {
Pair predictedPT = getPumpedPT(ai, source.getNetCombatDamage(), source.getNetToughness());
int oppT = Aggregates.sum(potentialBlockers, CardPredicates.Accessors.fnGetNetToughness);
- if (potentialBlockers.isEmpty() || (sa.getHostCard().hasKeyword("Trample") && predictedPT.getLeft() - oppT >= oppLife)) {
+ if (potentialBlockers.isEmpty() || (source.hasKeyword(Keyword.TRAMPLE) && predictedPT.getLeft() - oppT >= oppLife)) {
return true;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
index 06e690f12e6..902691478f8 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
@@ -13,6 +13,7 @@ import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostSacrifice;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -94,7 +95,7 @@ public class AttachAi extends SpellAbilityAi {
final CardCollection targets = CardLists.filter(list, new Predicate() {
@Override
public boolean apply(final Card c) {
- return !(c.hasProtectionFrom(source) || c.hasKeyword("Shroud") || c.hasKeyword("Hexproof"));
+ return !(c.hasProtectionFrom(source) || c.hasKeyword(Keyword.SHROUD) || c.hasKeyword(Keyword.HEXPROOF));
}
});
if (targets.isEmpty()) {
@@ -255,7 +256,7 @@ public class AttachAi extends SpellAbilityAi {
@Override
public boolean apply(final Card c) {
// Don't do Untapped Vigilance cards
- if (c.isCreature() && c.hasKeyword("Vigilance") && c.isUntapped()) {
+ if (c.isCreature() && c.hasKeyword(Keyword.VIGILANCE) && c.isUntapped()) {
return false;
}
@@ -397,7 +398,7 @@ public class AttachAi extends SpellAbilityAi {
List evenBetterList = CardLists.filter(betterList, new Predicate() {
@Override
public boolean apply(final Card c) {
- return c.hasKeyword("Indestructible") || c.hasKeyword("Hexproof");
+ return c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.hasKeyword(Keyword.HEXPROOF);
}
});
if (!evenBetterList.isEmpty()) {
@@ -484,23 +485,23 @@ public class AttachAi extends SpellAbilityAi {
for (Card card : list) {
int cardPriority = 0;
// Prefer Evasion
- if (card.hasKeyword("Trample")) {
+ if (card.hasKeyword(Keyword.TRAMPLE)) {
cardPriority += 10;
}
- if (card.hasKeyword("Menace")) {
+ if (card.hasKeyword(Keyword.MENACE)) {
cardPriority += 10;
}
// Avoid this for Sleepers Robe?
- if (card.hasKeyword("Fear")) {
+ if (card.hasKeyword(Keyword.FEAR)) {
cardPriority += 15;
}
- if (card.hasKeyword("Flying")) {
+ if (card.hasKeyword(Keyword.FLYING)) {
cardPriority += 20;
}
- if (card.hasKeyword("Shadow")) {
+ if (card.hasKeyword(Keyword.SHADOW)) {
cardPriority += 30;
}
- if (card.hasKeyword("Horsemanship")) {
+ if (card.hasKeyword(Keyword.HORSEMANSHIP)) {
cardPriority += 40;
}
if (card.hasKeyword("Unblockable")) {
@@ -520,10 +521,10 @@ public class AttachAi extends SpellAbilityAi {
if (card.getCurrentPower() <= 0) {
cardPriority = -100;
}
- if (card.hasKeyword("Defender")) {
+ if (card.hasKeyword(Keyword.DEFENDER)) {
cardPriority = -100;
}
- if (card.hasKeyword("Indestructible")) {
+ if (card.hasKeyword(Keyword.INDESTRUCTIBLE)) {
cardPriority += 15;
}
if (cardPriority > priority) {
@@ -725,7 +726,7 @@ public class AttachAi extends SpellAbilityAi {
prefList = CardLists.filter(list, new Predicate() {
@Override
public boolean apply(final Card c) {
- if (!c.hasKeyword("Indestructible") && (c.getLethalDamage() <= Math.abs(tgh))) {
+ if (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && (c.getLethalDamage() <= Math.abs(tgh))) {
return true;
}
@@ -1012,7 +1013,7 @@ public class AttachAi extends SpellAbilityAi {
if (isUsefulAttachKeyword(keyword, c, sa, pow)) {
return true;
}
- if (c.hasKeyword("Infect") && pow >= 2) {
+ if (c.hasKeyword(Keyword.INFECT) && pow >= 2) {
// consider +2 power a significant bonus on Infect creatures
return true;
}
@@ -1040,7 +1041,7 @@ public class AttachAi extends SpellAbilityAi {
prefList = CardLists.filter(prefList, new Predicate() {
@Override
public boolean apply(final Card c) {
- return !c.isEnchanted() || c.hasKeyword("Hexproof");
+ return !c.isEnchanted() || c.hasKeyword(Keyword.HEXPROOF);
}
});
}
@@ -1375,7 +1376,7 @@ public class AttachAi extends SpellAbilityAi {
return false;
}
} else if (keyword.equals("First Strike")) {
- if (card.getNetCombatDamage() + powerBonus <= 0 || card.hasKeyword("Double Strike")
+ if (card.getNetCombatDamage() + powerBonus <= 0 || card.hasKeyword(Keyword.DOUBLE_STRIKE)
|| (!ComputerUtilCombat.canAttackNextTurn(card) && !CombatUtil.canBlock(card, true))) {
return false;
}
@@ -1408,7 +1409,7 @@ public class AttachAi extends SpellAbilityAi {
return false;
}
} else if (keyword.equals("Reach")) {
- if (card.hasKeyword("Flying") || !CombatUtil.canBlock(card, true)) {
+ if (card.hasKeyword(Keyword.FLYING) || !CombatUtil.canBlock(card, true)) {
return false;
}
} else if (keyword.endsWith("CARDNAME can block an additional creature each combat.")) {
@@ -1417,11 +1418,11 @@ public class AttachAi extends SpellAbilityAi {
return false;
}
} else if (keyword.equals("CARDNAME can attack as though it didn't have defender.")) {
- if (!card.hasKeyword("Defender") || card.getNetCombatDamage() + powerBonus <= 0) {
+ if (!card.hasKeyword(Keyword.DEFENDER) || card.getNetCombatDamage() + powerBonus <= 0) {
return false;
}
} else if (keyword.equals("Shroud") || keyword.equals("Hexproof")) {
- if (card.hasKeyword("Shroud") || card.hasKeyword("Hexproof")) {
+ if (card.hasKeyword(Keyword.SHROUD) || card.hasKeyword(Keyword.HEXPROOF)) {
return false;
}
} else if (keyword.equals("Defender")) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/BecomesBlockedAi.java b/forge-ai/src/main/java/forge/ai/ability/BecomesBlockedAi.java
index 2d42c358ece..f3c44f1caa0 100644
--- a/forge-ai/src/main/java/forge/ai/ability/BecomesBlockedAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/BecomesBlockedAi.java
@@ -7,6 +7,7 @@ import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -30,7 +31,7 @@ public class BecomesBlockedAi extends SpellAbilityAi {
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), aiPlayer.getOpponents());
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
list = CardLists.getTargetableCards(list, sa);
- list = CardLists.getNotKeyword(list, "Trample");
+ list = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
while (sa.getTargets().getNumTargeted() < tgt.getMaxTargets(source, sa)) {
Card choice = null;
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 8278642c9c3..dc23c4cdb7c 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
@@ -22,6 +22,7 @@ import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CounterType;
import forge.game.combat.Combat;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
@@ -126,8 +127,8 @@ public class ChooseCardAi extends SpellAbilityAi {
} else if (aiLogic.equals("Duneblast")) {
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
- aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
- oppCreatures = CardLists.getNotKeyword(oppCreatures, "Indestructible");
+ aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
+ oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
// Use it as a wrath, when the human creatures threat the ai's life
if (aiCreatures.isEmpty() && ComputerUtilCombat.sumDamageIfUnblocked(oppCreatures, ai) >= ai.getLife()) {
@@ -261,7 +262,7 @@ public class ChooseCardAi extends SpellAbilityAi {
}
} else if (logic.equals("Duneblast")) {
CardCollectionView aiCreatures = ai.getCreaturesInPlay();
- aiCreatures = CardLists.getNotKeyword(aiCreatures, "Indestructible");
+ aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
if (aiCreatures.isEmpty()) {
return null;
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 8dd6367db26..2535c29505d 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersAi.java
@@ -27,6 +27,7 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
+import forge.game.keyword.Keyword;
import forge.util.Aggregates;
@@ -59,7 +60,7 @@ public abstract class CountersAi {
if (type.equals("M1M1")) {
// try to kill the best killable creature, or reduce the best one
// but try not to target a Undying Creature
- final List killable = CardLists.getNotKeyword(CardLists.filterToughness(list, amount), "Undying");
+ final List killable = CardLists.getNotKeyword(CardLists.filterToughness(list, amount), Keyword.UNDYING);
if (killable.size() > 0) {
choice = ComputerUtilCard.getBestCreatureAI(killable);
} else {
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 42e66c01d0a..cd82c59a5e5 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java
@@ -8,6 +8,7 @@ import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -286,7 +287,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 (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword("Undying") || card.isToken()) {
+ if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken()) {
return true;
}
return false;
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 f2d884ac38f..3a2f2777dc1 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
@@ -16,6 +16,7 @@ import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostRemoveCounter;
import forge.game.cost.CostSacrifice;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -157,7 +158,7 @@ public class CountersPutAi extends SpellAbilityAi {
// receive counters, execpt it has undying
CardCollection oppCreat = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterType.M1M1));
- oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, "Undying");
+ oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
oppCreatM1 = CardLists.filter(oppCreatM1, new Predicate() {
@Override
@@ -359,14 +360,8 @@ public class CountersPutAi extends SpellAbilityAi {
if ("Polukranos".equals(logic)) {
- CardCollection humCreatures = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
+ CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
- final CardCollection targets = CardLists.filter(humCreatures, new Predicate() {
- @Override
- public boolean apply(final Card c) {
- return !(c.hasProtectionFrom(source) || c.hasKeyword("Shroud") || c.hasKeyword("Hexproof"));
- }
- });
if (!targets.isEmpty()){
boolean canSurvive = false;
for (Card humanCreature : targets) {
@@ -811,12 +806,12 @@ public class CountersPutAi extends SpellAbilityAi {
final List creats = player.getCreaturesInPlay();
final int tributeAmount = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("CounterNum"), sa);
- final boolean isHaste = source.hasKeyword("Haste");
+ final boolean isHaste = source.hasKeyword(Keyword.HASTE);
List threatening = CardLists.filter(creats, new Predicate() {
@Override
public boolean apply(Card c) {
return CombatUtil.canBlock(source, c, !isHaste)
- && (c.getNetToughness() > source.getNetPower() + tributeAmount || c.hasKeyword("DeathTouch"));
+ && (c.getNetToughness() > source.getNetPower() + tributeAmount || c.hasKeyword(Keyword.DEATHTOUCH));
}
});
if (!threatening.isEmpty()) {
@@ -833,7 +828,7 @@ public class CountersPutAi extends SpellAbilityAi {
List canBlock = CardLists.filter(creats, new Predicate() {
@Override
public boolean apply(Card c) {
- return CombatUtil.canBlock(source, c) && (c.getNetToughness() > source.getNetPower() || c.hasKeyword("DeathTouch"));
+ return CombatUtil.canBlock(source, c) && (c.getNetToughness() > source.getNetPower() || c.hasKeyword(Keyword.DEATHTOUCH));
}
});
if (!canBlock.isEmpty()) {
@@ -926,7 +921,7 @@ public class CountersPutAi extends SpellAbilityAi {
final CardCollection persist = CardLists.filter(filtered, new Predicate() {
@Override
public boolean apply(Card input) {
- if (!input.hasKeyword("Persist"))
+ if (!input.hasKeyword(Keyword.PERSIST))
return false;
return input.getCounters(CounterType.M1M1) <= amount;
}
@@ -939,7 +934,7 @@ public class CountersPutAi extends SpellAbilityAi {
final CardCollection undying = CardLists.filter(filtered, new Predicate() {
@Override
public boolean apply(Card input) {
- if (!input.hasKeyword("Undying"))
+ if (!input.hasKeyword(Keyword.UNDYING))
return false;
return input.getCounters(CounterType.P1P1) <= amount && input.getNetToughness() > amount;
}
@@ -964,7 +959,7 @@ 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("Undying")) {
+ if (options.contains(CounterType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
return CounterType.M1M1;
}
for (CounterType type : options) {
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 6df50568446..4195a7924b7 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java
@@ -23,6 +23,7 @@ import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.card.*;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
@@ -56,7 +57,6 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
}
private boolean doTgt(Player ai, SpellAbility sa, boolean mandatory) {
- final Card source = sa.getHostCard();
final Game game = ai.getGame();
final int amount = Integer.valueOf(sa.getParam("CounterNum"));
@@ -71,7 +71,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
}
// Filter AI-specific targets if provided
- list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, false);
+ list = ComputerUtil.filterAITgts(sa, ai, list, false);
if (sa.hasParam("CounterType")) {
// currently only Jhoira's Timebug
@@ -125,7 +125,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
- CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
+ CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
if (!aiPersistList.isEmpty()) {
aiM1M1List = aiPersistList;
}
@@ -137,7 +137,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
// do as P1P1 part
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
- CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
+ CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
if (!aiUndyingList.isEmpty()) {
aiP1P1List = aiUndyingList;
@@ -226,9 +226,9 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
return CounterType.ICE;
}
- } else if (tgt.hasKeyword("Undying") && options.contains(CounterType.P1P1)) {
+ } else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.P1P1)) {
return CounterType.P1P1;
- } else if (tgt.hasKeyword("Persist") && options.contains(CounterType.M1M1)) {
+ } else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.M1M1)) {
return CounterType.M1M1;
}
@@ -272,9 +272,9 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
return false;
}
- } else if (type.equals(CounterType.M1M1) && tgt.hasKeyword("Persist")) {
+ } else if (type.equals(CounterType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
return false;
- } else if (type.equals(CounterType.P1P1) && tgt.hasKeyword("Undying")) {
+ } else if (type.equals(CounterType.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 ba2043efa14..5c500ecd7f2 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java
@@ -9,6 +9,7 @@ import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -188,7 +189,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
- CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, "Persist");
+ CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
if (!aiPersistList.isEmpty()) {
aiM1M1List = aiPersistList;
}
@@ -200,7 +201,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
// do as P1P1 part
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
- CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, "Undying");
+ CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
if (!aiUndyingList.isEmpty()) {
aiP1P1List = aiUndyingList;
@@ -230,7 +231,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
CardCollection aiList = CardLists.filterControlledBy(list, ai);
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
- CardCollection aiPersist = CardLists.getKeyword(aiList, "Persist");
+ CardCollection aiPersist = CardLists.getKeyword(aiList, Keyword.PERSIST);
if (!aiPersist.isEmpty()) {
aiList = aiPersist;
}
@@ -253,7 +254,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
// targeting ai creatures too
CardCollection aiList = CardLists.filterControlledBy(list, ai);
if (!aiList.isEmpty()) {
- CardCollection aiListUndying = CardLists.getKeyword(aiList, "Undying");
+ CardCollection aiListUndying = CardLists.getKeyword(aiList, Keyword.UNDYING);
if (!aiListUndying.isEmpty()) {
aiList = aiListUndying;
}
@@ -266,7 +267,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
// need to target opponent creatures
CardCollection oppList = CardLists.filterControlledBy(list, ai.getOpponents());
if (!oppList.isEmpty()) {
- CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, "Undying");
+ CardCollection oppListNotUndying = CardLists.getNotKeyword(oppList, Keyword.UNDYING);
if (!oppListNotUndying.isEmpty()) {
oppList = oppListNotUndying;
}
@@ -358,9 +359,9 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
}
} else {
- if (options.contains(CounterType.M1M1) && target.hasKeyword("Persist")) {
+ if (options.contains(CounterType.M1M1) && target.hasKeyword(Keyword.PERSIST)) {
return CounterType.M1M1;
- } else if (options.contains(CounterType.P1P1) && target.hasKeyword("Undying")) {
+ } else if (options.contains(CounterType.P1P1) && target.hasKeyword(Keyword.UNDYING)) {
return CounterType.M1M1;
}
for (CounterType type : options) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java
index b252db592b1..49f864cd4e5 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java
@@ -8,6 +8,7 @@ import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardCollectionView;
import forge.game.card.CardPredicates;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -24,12 +25,14 @@ public abstract class DamageAiBase extends SpellAbilityAi {
// Do not target a player if they aren't below 75% of our health.
// Unless Lifelink will cancel the damage to us
Card hostcard = sa.getHostCard();
- boolean lifelink = hostcard.hasKeyword("Lifelink");
- for (Card ench : hostcard.getEnchantedBy(false)) {
- // Treat cards enchanted by older cards with "when enchanted creature deals damage, gain life" as if they had lifelink.
- if (ench.hasSVar("LikeLifeLink")) {
- if ("True".equals(ench.getSVar("LikeLifeLink"))) {
- lifelink = true;
+ boolean lifelink = hostcard.hasKeyword(Keyword.LIFELINK);
+ if (!lifelink) {
+ for (Card ench : hostcard.getEnchantedBy(false)) {
+ // Treat cards enchanted by older cards with "when enchanted creature deals damage, gain life" as if they had lifelink.
+ if (ench.hasSVar("LikeLifeLink")) {
+ if ("True".equals(ench.getSVar("LikeLifeLink"))) {
+ lifelink = true;
+ }
}
}
}
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 8f2e82a901f..e43dfac49ab 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageAllAi.java
@@ -8,6 +8,7 @@ import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CounterType;
import forge.game.cost.Cost;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -268,7 +269,7 @@ public class DamageAllAi extends SpellAbilityAi {
}
};
- list = CardLists.getNotKeyword(list, "Indestructible");
+ list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
list = CardLists.filter(list, filterKillable);
return list;
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 e62b2f230d7..1e78c806dda 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
@@ -10,6 +10,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.cost.CostRemoveCounter;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -742,7 +743,7 @@ public class DamageDealAi extends DamageAiBase {
if (o instanceof Card) {
Card c = (Card) o;
final int restDamage = ComputerUtilCombat.predictDamageTo(c, dmg, saMe.getHostCard(), false);
- if (!c.hasKeyword("Indestructible") && ComputerUtilCombat.getDamageToKill(c) <= restDamage) {
+ if (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && ComputerUtilCombat.getDamageToKill(c) <= restDamage) {
if (c.getController().equals(ai)) {
return false;
} else {
@@ -942,7 +943,7 @@ public class DamageDealAi extends DamageAiBase {
for (Card c : creatures) {
int power = c.getNetPower();
int toughness = c.getNetToughness();
- boolean canDie = !(c.hasKeyword("Indestructible") || ComputerUtil.canRegenerate(c.getController(), c));
+ boolean canDie = !(c.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(c.getController(), c));
// Currently will target creatures with toughness 3+ (or power 5+)
// and only if the creature can actually die, do not "underdrain"
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 e7e89fb8b95..742692e5c1d 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
@@ -8,6 +8,7 @@ import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostSacrifice;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -109,7 +110,7 @@ public class DestroyAi extends SpellAbilityAi {
return false;
}
for (Card c : list) {
- if (c.hasKeyword("Indestructible")) {
+ if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
sa.getTargets().add(c);
return true;
}
@@ -133,7 +134,7 @@ public class DestroyAi extends SpellAbilityAi {
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, (CardCollection)list, true);
- list = CardLists.getNotKeyword(list, "Indestructible");
+ list = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
if (CardLists.getNotType(list, "Creature").isEmpty()) {
list = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, list, false);
}
@@ -160,7 +161,7 @@ public class DestroyAi extends SpellAbilityAi {
return false;
}
//Check for undying
- return (!c.hasKeyword("Undying") || c.getCounters(CounterType.P1P1) > 0);
+ return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterType.P1P1) > 0);
}
});
}
@@ -294,7 +295,7 @@ public class DestroyAi extends SpellAbilityAi {
if (list.isEmpty()
|| !CardLists.filterControlledBy(list, ai).isEmpty()
- || CardLists.getNotKeyword(list, "Indestructible").isEmpty()) {
+ || CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
return false;
}
}
@@ -316,7 +317,7 @@ public class DestroyAi extends SpellAbilityAi {
return false;
}
- CardCollection preferred = CardLists.getNotKeyword(list, "Indestructible");
+ CardCollection preferred = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
preferred = ComputerUtilCard.prioritizeCreaturesWorthRemovingNow(ai, preferred, false);
diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
index 7a8e7343b61..b2b06161f29 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
@@ -7,6 +7,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -18,7 +19,7 @@ public class DestroyAllAi extends SpellAbilityAi {
private static final Predicate predicate = new Predicate() {
@Override
public boolean apply(final Card c) {
- return !(c.hasKeyword("Indestructible") || c.getSVar("SacMe").length() > 0);
+ return !(c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getSVar("SacMe").length() > 0);
}
};
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 16f62f6c40f..59978ac5bee 100644
--- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java
@@ -17,6 +17,7 @@ import forge.game.GlobalRuleChange;
import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.combat.CombatUtil;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -138,7 +139,8 @@ public class EffectAi extends SpellAbilityAi {
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate() {
@Override
public boolean apply(final Card c) {
- return (c.isInstant() || c.isSorcery()) && !c.hasKeyword("Rebound") && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
+ return (c.isInstant() || c.isSorcery()) && !c.hasKeyword(Keyword.REBOUND)
+ && ComputerUtil.hasReasonToPlayCardThisTurn(ai, c);
}
});
diff --git a/forge-ai/src/main/java/forge/ai/ability/FightAi.java b/forge-ai/src/main/java/forge/ai/ability/FightAi.java
index d597b5cfa6d..f9805e09954 100644
--- a/forge-ai/src/main/java/forge/ai/ability/FightAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/FightAi.java
@@ -7,6 +7,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
@@ -162,15 +163,15 @@ public class FightAi extends SpellAbilityAi {
// Get sorted creature lists
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection humCreatures = ai.getOpponents().getCreaturesInPlay();
- if ("Time to Feed".equals(sourceName)) { // flip sa
- aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
- aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
- humCreatures = CardLists.getTargetableCards(humCreatures, sa);
- } else {
- aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
- aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
- humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
- }
+ if ("Time to Feed".equals(sourceName)) { // flip sa
+ aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
+ aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
+ humCreatures = CardLists.getTargetableCards(humCreatures, sa);
+ } else {
+ aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
+ aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
+ humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
+ }
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
@@ -246,17 +247,19 @@ public class FightAi extends SpellAbilityAi {
}
return false;
}
+
public static boolean canKill(Card fighter, Card opponent, int pumpAttack) {
- if (opponent.getSVar("Targeting").equals("Dies")) {
- return true;
- }
- if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed()
- || opponent.getShieldCount() > 0 || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
- return false;
- }
- if (fighter.hasKeyword("Deathtouch") || ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
- return true;
- }
- return false;
+ if (opponent.getSVar("Targeting").equals("Dies")) {
+ return true;
+ }
+ if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed() || opponent.getShieldCount() > 0
+ || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
+ return false;
+ }
+ if (fighter.hasKeyword(Keyword.DEATHTOUCH)
+ || ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
+ return true;
+ }
+ return false;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/FogAi.java b/forge-ai/src/main/java/forge/ai/ability/FogAi.java
index 00a7386ea7d..09034ee8d7a 100644
--- a/forge-ai/src/main/java/forge/ai/ability/FogAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/FogAi.java
@@ -6,6 +6,7 @@ import forge.game.Game;
import forge.game.GameObject;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -66,7 +67,7 @@ public class FogAi extends SpellAbilityAi {
for (Card atk : game.getCombat().getAttackersOf(ai)) {
if (game.getCombat().isUnblocked(atk)) {
dmg += atk.getNetCombatDamage();
- } else if (atk.hasKeyword("Trample")) {
+ } else if (atk.hasKeyword(Keyword.TRAMPLE)) {
dmg += atk.getNetCombatDamage() - Aggregates.sum(game.getCombat().getBlockers(atk), CardPredicates.Accessors.fnGetNetToughness);
}
}
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 6c7bf2dc1aa..12292b4e44e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
@@ -9,6 +9,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.cost.CostPart;
import forge.game.cost.CostRemoveCounter;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -170,7 +171,7 @@ public class ManaEffectAi extends SpellAbilityAi {
}
testSaNoCost.setActivatingPlayer(ai);
if (((PlayerControllerAi)ai.getController()).getAi().canPlaySa(testSaNoCost) == AiPlayDecision.WillPlay) {
- if (testSa.getHostCard().isPermanent() && !testSa.getHostCard().hasKeyword("Haste")
+ if (testSa.getHostCard().isPermanent() && !testSa.getHostCard().hasKeyword(Keyword.HASTE)
&& !ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
// AI will waste a ritual in Main 1 unless the casted permanent is a haste creature
continue;
diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java
index 3f26a5b377f..1b5dd90d4b1 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java
@@ -80,7 +80,7 @@ public class PermanentCreatureAi extends PermanentAi {
}
// save cards with flash for surprise blocking
- if (card.hasKeyword("Flash")
+ if (card.withFlash(ai)
&& (ai.isUnlimitedHandSize() || ai.getCardsIn(ZoneType.Hand).size() <= ai.getMaxHandSize()
|| ph.getPhase().isBefore(PhaseType.END_OF_TURN))
&& ai.getManaPool().totalMana() <= 0
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 89e3257d7f7..74aa623da32 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PlayAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PlayAi.java
@@ -11,6 +11,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.cost.Cost;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.Spell;
@@ -74,7 +75,7 @@ public class PlayAi extends SpellAbilityAi {
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
}
- if (source != null && source.hasKeyword("Hideaway") && source.hasRemembered()) {
+ if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) {
// AI is not very good at playing non-permanent spells this way, at least yet
// (might be possible to enable it for Sorceries in Main1/Main2 if target is available,
// but definitely not for most Instants)
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 2d6234fee8b..78d11c5cd30 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java
@@ -14,6 +14,7 @@ import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostRemoveCounter;
import forge.game.cost.CostTapType;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -35,12 +36,7 @@ public class PumpAi extends PumpAiBase {
if (cost == null) {
return true;
}
- for (final CostPart part : cost.getCostParts()) {
- if (part instanceof CostTapType) {
- return true;
- }
- }
- return false;
+ return cost.hasSpecificCostType(CostTapType.class);
}
@Override
@@ -190,7 +186,7 @@ public class PumpAi extends PumpAiBase {
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
- if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword("Undying")
+ if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|| card.isToken()) {
return true;
}
@@ -243,7 +239,7 @@ public class PumpAi extends PumpAiBase {
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
- if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword("Undying")
+ if (srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|| card.isToken()) {
return true;
}
@@ -775,7 +771,7 @@ public class PumpAi extends PumpAiBase {
if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) {
if (source.isCreature()) {
- if (!source.hasKeyword("Indestructible") && source.getNetToughness() + defense <= source.getDamage()) {
+ if (!source.hasKeyword(Keyword.INDESTRUCTIBLE) && source.getNetToughness() + defense <= source.getDamage()) {
return false;
}
if (source.getNetToughness() + defense <= 0) {
@@ -864,7 +860,7 @@ public class PumpAi extends PumpAiBase {
final Player defPlayer = combat.getDefendingPlayerRelatedTo(source);
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;
- final boolean isInfect = source.hasKeyword("Infect"); // Flesh-Eater Imp
+ 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)) {
@@ -970,7 +966,7 @@ public class PumpAi extends PumpAiBase {
final Player defPlayer = combat.getDefendingPlayerRelatedTo(source);
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;
- final boolean isInfect = source.hasKeyword("Infect");
+ final boolean isInfect = source.hasKeyword(Keyword.INFECT);
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.POISON)) {
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 5b2a6f756ba..89af0bf5722 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
@@ -12,6 +12,7 @@ import forge.game.Game;
import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.phase.Untap;
@@ -135,7 +136,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|| card.getNetCombatDamage() <= 0
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|| ph.getPhase().isBefore(PhaseType.MAIN1)
- || CardLists.getNotKeyword(ai.getCreaturesInPlay(), "Defender").isEmpty())) {
+ || CardLists.getNotKeyword(ai.getCreaturesInPlay(), Keyword.DEFENDER).isEmpty())) {
return false;
}
if (!ph.isPlayerTurn(ai) && (combat == null || !combat.isAttacking(card) || card.getNetCombatDamage() <= 0)) {
@@ -193,21 +194,26 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
} else if (keyword.endsWith("Flying")) {
+ CardCollectionView attackingFlyer = CardCollection.EMPTY;
+ if (combat != null) {
+ attackingFlyer = CardLists.getKeyword(combat.getAttackers(), Keyword.FLYING);
+ }
+
if (ph.isPlayerTurn(opp)
&& ph.getPhase() == PhaseType.COMBAT_DECLARE_ATTACKERS
- && !CardLists.getKeyword(game.getCombat().getAttackers(), "Flying").isEmpty()
- && !card.hasKeyword("Reach")
+ && !attackingFlyer.isEmpty()
+ && !card.hasKeyword(Keyword.REACH)
&& CombatUtil.canBlock(card)
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
return true;
}
- Predicate flyingOrReach = Predicates.or(CardPredicates.hasKeyword("Flying"), CardPredicates.hasKeyword("Reach"));
+ Predicate flyingOrReach = Predicates.or(CardPredicates.hasKeyword(Keyword.FLYING), CardPredicates.hasKeyword(Keyword.REACH));
if (ph.isPlayerTurn(opp) && combat != null
- && Iterables.any(combat.getAttackers(), CardPredicates.hasKeyword("Flying"))
+ && !attackingFlyer.isEmpty()
&& CombatUtil.canBlock(card)) {
// Use defensively to destroy the opposing Flying creature when possible, or to block with an indestructible
// creature buffed with Flying
- for (Card c : CardLists.filter(combat.getAttackers(), CardPredicates.hasKeyword("Flying"))) {
+ for (Card c : attackingFlyer) {
if (!ComputerUtilCombat.attackerCantBeDestroyedInCombat(c.getController(), c)
&& (card.getNetPower() >= c.getNetToughness() && card.getNetToughness() > c.getNetPower()
|| ComputerUtilCombat.attackerCantBeDestroyedInCombat(ai, card))) {
@@ -225,7 +231,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
} else if (keyword.endsWith("Horsemanship")) {
if (ph.isPlayerTurn(opp)
&& ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)
- && !CardLists.getKeyword(game.getCombat().getAttackers(), "Horsemanship").isEmpty()
+ && !CardLists.getKeyword(game.getCombat().getAttackers(), Keyword.HORSEMANSHIP).isEmpty()
&& CombatUtil.canBlock(card)
&& ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) {
return true;
@@ -234,7 +240,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| newPower <= 0
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
- "Horsemanship").isEmpty()) {
+ Keyword.HORSEMANSHIP).isEmpty()) {
return false;
}
} else if (keyword.endsWith("Intimidate")) {
@@ -298,9 +304,9 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
} else if (keyword.equals("First Strike")) {
- if (card.hasKeyword("Double Strike")) {
- return false;
- }
+ if (card.hasKeyword(Keyword.DOUBLE_STRIKE)) {
+ return false;
+ }
if (combat != null && combat.isBlocked(card) && !combat.getBlockers(card).isEmpty()) {
Card blocker = combat.getBlockers(card).get(0);
if (ComputerUtilCombat.canDestroyAttacker(ai, card, blocker, combat, true)
@@ -338,7 +344,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|| newPower <= 0
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
- "Flanking").isEmpty()) {
+ Keyword.FLANKING).isEmpty()) {
return false;
}
} else if (keyword.startsWith("Trample")) {
@@ -353,7 +359,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
if (newPower <= 0) {
return false;
}
- if (combat != null && combat.isBlocking(card) && !card.hasKeyword("Wither")) {
+ if (combat != null && combat.isBlocking(card) && !card.hasKeyword(Keyword.WITHER)) {
return true;
}
if ((ph.isPlayerTurn(opp))
@@ -362,7 +368,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
} else if (keyword.endsWith("Wither")) {
- if (newPower <= 0 || card.hasKeyword("Infect")) {
+ if (newPower <= 0 || card.hasKeyword(Keyword.INFECT)) {
return false;
}
return combat != null && ( combat.isBlocking(card) || (combat.isAttacking(card) && combat.isBlocked(card)) );
@@ -375,14 +381,14 @@ public abstract class PumpAiBase extends SpellAbilityAi {
if (ph.isPlayerTurn(opp) || !CombatUtil.canAttack(card, opp)
|| newPower <= 0
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
- || CardLists.getNotKeyword(opp.getCreaturesInPlay(), "Defender").isEmpty()) {
+ || CardLists.getNotKeyword(opp.getCreaturesInPlay(), Keyword.DEFENDER).isEmpty()) {
return false;
}
} else if (keyword.equals("Reach")) {
if (ph.isPlayerTurn(ai)
|| !ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)
- || CardLists.getKeyword(game.getCombat().getAttackers(), "Flying").isEmpty()
- || card.hasKeyword("Flying")
+ || CardLists.getKeyword(game.getCombat().getAttackers(), Keyword.FLYING).isEmpty()
+ || card.hasKeyword(Keyword.FLYING)
|| !CombatUtil.canBlock(card)) {
return false;
}
@@ -409,7 +415,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
} else if (keyword.equals("Persist")) {
- if (card.getBaseToughness() <= 1 || card.hasKeyword("Undying")) {
+ if (card.getBaseToughness() <= 1 || card.hasKeyword(Keyword.UNDYING)) {
return false;
}
} else if (keyword.equals("Islandwalk")) {
@@ -445,7 +451,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
} else if (keyword.endsWith("CARDNAME can attack as though it didn't have defender.")) {
- if (!ph.isPlayerTurn(ai) || !card.hasKeyword("Defender")
+ if (!ph.isPlayerTurn(ai) || !card.hasKeyword(Keyword.DEFENDER)
|| ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN)
|| card.isTapped() || newPower <= 0) {
return false;
@@ -510,7 +516,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
if (c.getSVar("Targeting").equals("Dies") || c.getNetToughness() <= -defense) {
return true; // can kill indestructible creatures
}
- return (ComputerUtilCombat.getDamageToKill(c) <= -defense && !c.hasKeyword("Indestructible"));
+ return (ComputerUtilCombat.getDamageToKill(c) <= -defense && !c.hasKeyword(Keyword.INDESTRUCTIBLE));
}
}); // leaves all creatures that will be destroyed
} // -X/-X end
diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java
index 6c14f2f5748..662ffc10a4c 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java
@@ -14,6 +14,7 @@ import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.combat.Combat;
import forge.game.cost.Cost;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -85,7 +86,7 @@ public class PumpAllAi extends PumpAiBase {
if (c.getNetToughness() <= -defense) {
return true; // can kill indestructible creatures
}
- return ((ComputerUtilCombat.getDamageToKill(c) <= -defense) && !c.hasKeyword("Indestructible"));
+ return ((ComputerUtilCombat.getDamageToKill(c) <= -defense) && !c.hasKeyword(Keyword.INDESTRUCTIBLE));
}
}); // leaves all creatures that will be destroyed
human = CardLists.filter(human, new Predicate() {
@@ -94,7 +95,7 @@ public class PumpAllAi extends PumpAiBase {
if (c.getNetToughness() <= -defense) {
return true; // can kill indestructible creatures
}
- return ((ComputerUtilCombat.getDamageToKill(c) <= -defense) && !c.hasKeyword("Indestructible"));
+ return ((ComputerUtilCombat.getDamageToKill(c) <= -defense) && !c.hasKeyword(Keyword.INDESTRUCTIBLE));
}
}); // leaves all creatures that will be destroyed
} // -X/-X end
diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java
index 79c634f2890..b66785bc0ee 100644
--- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java
@@ -8,6 +8,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
@@ -82,7 +83,7 @@ public class SacrificeAi extends SpellAbilityAi {
if (!destroy) {
list = CardLists.filter(list, CardPredicates.canBeSacrificedBy(sa));
} else {
- if (!CardLists.getKeyword(list, "Indestructible").isEmpty()) {
+ if (!CardLists.getKeyword(list, Keyword.INDESTRUCTIBLE).isEmpty()) {
// human can choose to destroy indestructibles
return false;
}
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 b1d5d86fe09..ec86828e6d8 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
@@ -14,6 +14,7 @@ import forge.game.combat.Combat;
import forge.game.cost.CostPart;
import forge.game.cost.CostPutCounter;
import forge.game.cost.CostRemoveCounter;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -200,29 +201,29 @@ public class TokenAi extends SpellAbilityAi {
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
- //Flash Foliage
- CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
- list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
- list = CardLists.getTargetableCards(list, sa);
- CardCollection betterList = CardLists.filter(list, new Predicate() {
- @Override
- public boolean apply(Card c) {
- return c.getLethalDamage() == 1;
- }
- });
- if (!betterList.isEmpty()) {
- list = betterList;
- }
- betterList = CardLists.getNotKeyword(list, "Trample");
- if (!betterList.isEmpty()) {
- list = betterList;
- }
- if (!list.isEmpty()) {
- sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
- } else {
- return false;
- }
-
+ // Flash Foliage
+ CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield),
+ ai.getOpponents());
+ list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
+ list = CardLists.getTargetableCards(list, sa);
+ CardCollection betterList = CardLists.filter(list, new Predicate() {
+ @Override
+ public boolean apply(Card c) {
+ return c.getLethalDamage() == 1;
+ }
+ });
+ if (!betterList.isEmpty()) {
+ list = betterList;
+ }
+ betterList = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
+ if (!betterList.isEmpty()) {
+ list = betterList;
+ }
+ if (!list.isEmpty()) {
+ sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
+ } else {
+ return false;
+ }
}
}
}
diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java
index d9f9db03937..210930785b7 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java
@@ -140,13 +140,13 @@ public class SpellAbilityPicker {
}
}
- private static boolean isSorcerySpeed(SpellAbility sa) {
+ private static boolean isSorcerySpeed(SpellAbility sa, Player player) {
// TODO: Can we use the actual rules engine for this instead of trying to do the logic ourselves?
if (sa instanceof PlayLandAbility) {
return false;
}
if (sa.isSpell()) {
- return !sa.getHostCard().isInstant() && !sa.getHostCard().hasKeyword("Flash");
+ return !sa.getHostCard().isInstant() && !sa.getHostCard().withFlash(player);
}
if (sa.getRestrictions().isPwAbility()) {
return !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed.");
@@ -167,7 +167,7 @@ public class SpellAbilityPicker {
if (currentPhase.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
List candidateSAs2 = new ArrayList();
for (SpellAbility sa : candidateSAs) {
- if (!isSorcerySpeed(sa)) {
+ if (!isSorcerySpeed(sa, player)) {
System.err.println("Not sorcery: " + sa);
candidateSAs2.add(sa);
}
diff --git a/forge-core/src/main/java/forge/util/Visitor.java b/forge-core/src/main/java/forge/util/Visitor.java
index 029218000b4..2a36ee062ee 100644
--- a/forge-core/src/main/java/forge/util/Visitor.java
+++ b/forge-core/src/main/java/forge/util/Visitor.java
@@ -1,11 +1,22 @@
package forge.util;
public abstract class Visitor {
- public abstract void visit(T object);
+ /**
+ * visit the object
+ * the Visitor should return true it can be visit again
+ * returning false means the outer function can stop
+ *
+ * @param object
+ * @return boolean
+ */
+ public abstract boolean visit(T object);
- public void visitAll(Iterable extends T> objects) {
+ public boolean visitAll(Iterable extends T> objects) {
for (T obj : objects) {
- visit(obj);
+ if (!visit(obj)) {
+ return false;
+ }
}
+ return true;
}
}
diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java
index 0f82a49665c..f588e5a222a 100644
--- a/forge-game/src/main/java/forge/game/Game.java
+++ b/forge-game/src/main/java/forge/game/Game.java
@@ -527,25 +527,57 @@ public class Game {
return cards;
}
- public Card getCardState(final Card card) {
- for (final Card c : getCardsInGame()) {
- if (card.equals(c)) {
- return c;
- }
+ private static class CardStateVisitor extends Visitor {
+ Card found = null;
+ Card old = null;
+
+ private CardStateVisitor(final Card card) {
+ this.old = card;
}
- return card;
+
+ @Override
+ public boolean visit(Card object) {
+ if (object.equals(old)) {
+ found = object;
+ }
+ return found == null;
+ }
+
+ public Card getFound() {
+ return found == null ? found : old;
+ }
+ }
+
+ public Card getCardState(final Card card) {
+ CardStateVisitor visit = new CardStateVisitor(card);
+ 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()) {
- visitor.visitAll(player.getZone(ZoneType.Graveyard).getCards());
- visitor.visitAll(player.getZone(ZoneType.Hand).getCards());
- visitor.visitAll(player.getZone(ZoneType.Library).getCards());
- visitor.visitAll(player.getZone(ZoneType.Battlefield).getCards(false));
- visitor.visitAll(player.getZone(ZoneType.Exile).getCards());
- visitor.visitAll(player.getZone(ZoneType.Command).getCards());
- visitor.visitAll(player.getInboundTokens());
+ if (!visitor.visitAll(player.getZone(ZoneType.Graveyard).getCards())) {
+ return;
+ }
+ if (!visitor.visitAll(player.getZone(ZoneType.Hand).getCards())) {
+ return;
+ }
+ if (!visitor.visitAll(player.getZone(ZoneType.Library).getCards())) {
+ return;
+ }
+ if (!visitor.visitAll(player.getZone(ZoneType.Battlefield).getCards(false))) {
+ return;
+ }
+ if (!visitor.visitAll(player.getZone(ZoneType.Exile).getCards())) {
+ return;
+ }
+ if (!visitor.visitAll(player.getZone(ZoneType.Command).getCards())) {
+ return;
+ }
+ if (!visitor.visitAll(player.getInboundTokens())) {
+ return;
+ }
}
visitor.visitAll(getStackZone().getCards());
}
@@ -553,8 +585,9 @@ public class Game {
final CardCollection all = new CardCollection();
Visitor visitor = new Visitor() {
@Override
- public void visit(Card card) {
+ public boolean visit(Card card) {
all.add(card);
+ return true;
}
};
forEachCardInGame(visitor);
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index 7c84dc29923..87618eaa0eb 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -792,7 +792,7 @@ public class GameAction {
game.forEachCardInGame(new Visitor() {
@Override
- public void visit(final Card c) {
+ public boolean visit(final Card c) {
// need to get Card from preList if able
final Card co = preList.get(c);
for (int i = 0; i < co.getStaticAbilities().size(); i++) {
@@ -808,6 +808,7 @@ public class GameAction {
if (!co.getStaticCommandList().isEmpty()) {
staticList.add(co);
}
+ return true;
}
});
diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java
index 70295ebc2b3..5eea36a7da2 100644
--- a/forge-game/src/main/java/forge/game/GameActionUtil.java
+++ b/forge-game/src/main/java/forge/game/GameActionUtil.java
@@ -31,6 +31,7 @@ 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.spellability.*;
@@ -482,8 +483,8 @@ public final class GameActionUtil {
}
}
- if (source.hasKeyword("Conspire")) {
- int amount = source.getAmountOfKeyword("Conspire");
+ if (source.hasKeyword(Keyword.CONSPIRE)) {
+ int amount = source.getAmountOfKeyword(Keyword.CONSPIRE);
for (int kwInstance = 1; kwInstance <= amount; kwInstance++) {
for (int i = 0; i < abilities.size(); i++) {
final SpellAbility newSA = abilities.get(i).copy();
diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java
index 1cdfd7997ef..61c5ad6457a 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.CardDamageMap;
import forge.game.card.CounterType;
import forge.game.event.GameEventCardAttachment;
import forge.game.event.GameEventCardAttachment.AttachMethod;
+import forge.game.keyword.Keyword;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.util.collect.FCollection;
@@ -275,6 +276,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
}
public abstract boolean hasKeyword(final String keyword);
+ public abstract boolean hasKeyword(final Keyword keyword);
// GameEntities can now be Enchanted
public final CardCollectionView getEnchantedBy(boolean allowModify) {
diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java
index e9c3f75c299..7ba0c4a52e0 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -1007,11 +1007,11 @@ public class Card extends GameEntity implements Comparable {
}
public final boolean hasFirstStrike() {
- return hasKeyword("First Strike");
+ return hasKeyword(Keyword.FIRST_STRIKE);
}
public final boolean hasDoubleStrike() {
- return hasKeyword("Double Strike");
+ return hasKeyword(Keyword.DOUBLE_STRIKE);
}
public final boolean hasSecondStrike() {
@@ -2338,11 +2338,11 @@ public class Card extends GameEntity implements Comparable {
}
public final boolean hasSickness() {
- return sickness && !hasKeyword("Haste");
+ return sickness && !hasKeyword(Keyword.HASTE);
}
public final boolean isSick() {
- return sickness && isCreature() && !hasKeyword("Haste");
+ return sickness && isCreature() && !hasKeyword(Keyword.HASTE);
}
public boolean hasBecomeTargetThisTurn() {
@@ -3217,6 +3217,15 @@ public class Card extends GameEntity implements Comparable {
visitHiddenExtreinsicKeywords(visitor);
}
+ @Override
+ public final boolean hasKeyword(Keyword keyword) {
+ return hasKeyword(keyword, currentState);
+ }
+
+ public final boolean hasKeyword(Keyword key, CardState state) {
+ return state.hasKeyword(key);
+ }
+
@Override
public final boolean hasKeyword(String keyword) {
return hasKeyword(keyword, currentState);
@@ -3227,9 +3236,9 @@ public class Card extends GameEntity implements Comparable {
keyword = keyword.substring(7);
}
- CountKeywordVisitor visitor = new CountKeywordVisitor(keyword);
+ HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, false);
visitKeywords(state, visitor);
- return visitor.getCount() > 0;
+ return visitor.getResult();
}
public final void updateKeywords() {
@@ -3345,20 +3354,26 @@ public class Card extends GameEntity implements Comparable {
}
}
- state.setCachedKeywords(keywords.getValues());
+ state.setCachedKeywords(keywords);
}
private void visitUnhiddenKeywords(CardState state, Visitor visitor) {
if (changedCardKeywords.isEmpty()) {
// Fast path that doesn't involve temp allocations.
for (KeywordInterface kw : state.getIntrinsicKeywords()) {
- visitor.visit(kw);
+ if (!visitor.visit(kw)) {
+ return;
+ }
}
for (KeywordInterface kw : extrinsicKeyword.getValues()) {
- visitor.visit(kw);
+ if (!visitor.visit(kw)) {
+ return;
+ }
}
} else {
for (KeywordInterface kw : getUnhiddenKeywords(state)) {
- visitor.visit(kw);
+ if (!visitor.visit(kw)) {
+ return;
+ }
}
}
}
@@ -3571,7 +3586,9 @@ public class Card extends GameEntity implements Comparable {
}
private void visitHiddenExtreinsicKeywords(Visitor visitor) {
for (KeywordInterface inst : hiddenExtrinsicKeyword.getValues()) {
- visitor.visit(inst);
+ if (!visitor.visit(inst)) {
+ return;
+ }
}
}
@@ -3820,18 +3837,18 @@ public class Card extends GameEntity implements Comparable {
return hasStartOfKeyword(keyword, currentState);
}
public final boolean hasStartOfKeyword(String keyword, CardState state) {
- CountKeywordVisitor visitor = new CountKeywordVisitor(keyword, true);
+ HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true);
visitKeywords(state, visitor);
- return visitor.getCount() > 0;
+ return visitor.getResult();
}
public final boolean hasStartOfUnHiddenKeyword(String keyword) {
return hasStartOfUnHiddenKeyword(keyword, currentState);
}
public final boolean hasStartOfUnHiddenKeyword(String keyword, CardState state) {
- CountKeywordVisitor visitor = new CountKeywordVisitor(keyword, true);
+ HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true);
visitUnhiddenKeywords(state, visitor);
- return visitor.getCount() > 0;
+ return visitor.getResult();
}
public final boolean hasAnyKeyword(final Iterable keywords) {
@@ -3856,25 +3873,42 @@ public class Card extends GameEntity implements Comparable {
return visitor.getCount();
}
+ public final int getAmountOfKeyword(final Keyword k) {
+ return getAmountOfKeyword(k, currentState);
+ }
+ public final int getAmountOfKeyword(final Keyword k, CardState state) {
+ return state.getCachedKeyword(k).size();
+ }
+
// This is for keywords with a number like Bushido, Annihilator and Rampage.
// It returns the total.
- public final int getKeywordMagnitude(final String k) {
+ public final int getKeywordMagnitude(final Keyword k) {
return getKeywordMagnitude(k, currentState);
}
- public final int getKeywordMagnitude(final String k, CardState state) {
+
+ /**
+ * use it only for real keywords and not with hidden ones
+ *
+ * @param Keyword k
+ * @param CardState state
+ * @return Int
+ */
+ public final int getKeywordMagnitude(final Keyword k, CardState state) {
int count = 0;
- for (final KeywordInterface inst : getKeywords(state)) {
+ for (final KeywordInterface inst : state.getCachedKeyword(k)) {
String kw = inst.getOriginal();
- if (kw.startsWith(k)) {
- final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" ");
- final String s = parse[1];
- if (StringUtils.isNumeric(s)) {
- count += Integer.parseInt(s);
- } else {
- String svar = StringUtils.join(parse);
- if (state.hasSVar(svar)) {
- count += AbilityUtils.calculateAmount(this, state.getSVar(svar), null);
- }
+ // this can't be used yet for everything because of X values in Bushido X
+ // KeywordInterface#getAmount
+ // KeywordCollection#getAmount
+
+ final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" ");
+ final String s = parse[1];
+ if (StringUtils.isNumeric(s)) {
+ count += Integer.parseInt(s);
+ } else {
+ String svar = StringUtils.join(parse);
+ if (state.hasSVar(svar)) {
+ count += AbilityUtils.calculateAmount(this, state.getSVar(svar), null);
}
}
}
@@ -4538,7 +4572,7 @@ public class Card extends GameEntity implements Comparable {
final Game game = source.getGame();
boolean wither = (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.alwaysWither)
- || source.hasKeyword("Wither") || source.hasKeyword("Infect"));
+ || source.hasKeyword(Keyword.WITHER) || source.hasKeyword(Keyword.INFECT));
if (isInPlay()) {
if (wither) {
@@ -4551,7 +4585,7 @@ public class Card extends GameEntity implements Comparable {
}
}
- if (source.hasKeyword("Deathtouch") && isCreature()) {
+ if (source.hasKeyword(Keyword.DEATHTOUCH) && isCreature()) {
setHasBeenDealtDeathtouchDamage(true);
damageType = DamageType.Deathtouch;
}
@@ -4943,7 +4977,7 @@ public class Card extends GameEntity implements Comparable {
}
public final boolean canBeDestroyed() {
- return isInPlay() && (!hasKeyword("Indestructible") || (isCreature() && getNetToughness() <= 0));
+ return isInPlay() && (!hasKeyword(Keyword.INDESTRUCTIBLE) || (isCreature() && getNetToughness() <= 0));
}
public final boolean canBeSacrificed() {
@@ -4983,10 +5017,7 @@ public class Card extends GameEntity implements Comparable {
final MutableBoolean result = new MutableBoolean(true);
visitKeywords(currentState, new Visitor() {
@Override
- public void visit(KeywordInterface kw) {
- if (result.isFalse()) {
- return;
- }
+ public boolean visit(KeywordInterface kw) {
switch (kw.getOriginal()) {
case "Shroud":
StringBuilder sb = new StringBuilder();
@@ -5015,6 +5046,7 @@ public class Card extends GameEntity implements Comparable {
}
break;
}
+ return result.isTrue();
}
});
if (result.isFalse()) {
@@ -5506,11 +5538,12 @@ public class Card extends GameEntity implements Comparable {
}
@Override
- public void visit(KeywordInterface inst) {
+ public boolean visit(KeywordInterface inst) {
final String kw = inst.getOriginal();
if ((startOf && kw.startsWith(keyword)) || kw.equals(keyword)) {
count++;
}
+ return true;
}
public int getCount() {
@@ -5518,13 +5551,38 @@ public class Card extends GameEntity implements Comparable {
}
}
+ private static final class HasKeywordVisitor extends Visitor {
+ private String keyword;
+ private final MutableBoolean result = new MutableBoolean(false);
+
+ private boolean startOf;
+ private HasKeywordVisitor(String keyword, boolean startOf) {
+ this.keyword = keyword;
+ this.startOf = startOf;
+ }
+
+ @Override
+ public boolean visit(KeywordInterface inst) {
+ final String kw = inst.getOriginal();
+ if ((startOf && kw.startsWith(keyword)) || kw.equals(keyword)) {
+ result.setTrue();
+ }
+ return result.isFalse();
+ }
+
+ public boolean getResult() {
+ return result.isTrue();
+ }
+ }
+
// Collects all the keywords into a list.
private static final class ListKeywordVisitor extends Visitor {
private List keywords = Lists.newArrayList();
@Override
- public void visit(KeywordInterface kw) {
+ public boolean visit(KeywordInterface kw) {
keywords.add(kw);
+ return true;
}
public List getKeywords() {
@@ -5645,7 +5703,7 @@ public class Card extends GameEntity implements Comparable {
}
public boolean withFlash(Player p) {
- if (hasKeyword("Flash")) {
+ if (hasKeyword(Keyword.FLASH)) {
return true;
}
if (withFlash.containsValue(p)) {
diff --git a/forge-game/src/main/java/forge/game/card/CardDamageMap.java b/forge-game/src/main/java/forge/game/card/CardDamageMap.java
index e007f4725ae..13dc81b57f0 100644
--- a/forge-game/src/main/java/forge/game/card/CardDamageMap.java
+++ b/forge-game/src/main/java/forge/game/card/CardDamageMap.java
@@ -12,6 +12,7 @@ import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import forge.game.GameEntity;
+import forge.game.keyword.Keyword;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
@@ -53,7 +54,7 @@ public class CardDamageMap extends ForwardingTable {
sourceLKI.getGame().getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false);
- if (sourceLKI.hasKeyword("Lifelink")) {
+ if (sourceLKI.hasKeyword(Keyword.LIFELINK)) {
sourceLKI.getController().gainLife(sum, sourceLKI, sa);
}
}
diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
index 724c580f4ef..62f298bb84e 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
@@ -1196,7 +1196,7 @@ public class CardFactoryUtil {
}
if (sq[0].contains("BushidoPoint")) {
- return doXMath(c.getKeywordMagnitude("Bushido"), m, c);
+ return doXMath(c.getKeywordMagnitude(Keyword.BUSHIDO), m, c);
}
if (sq[0].contains("TimesKicked")) {
return doXMath(c.getKickerMagnitude(), m, c);
@@ -4036,7 +4036,7 @@ public class CardFactoryUtil {
return false;
}
- if (this.getHostCard().isInstant() || this.getHostCard().hasKeyword("Flash")) {
+ if (this.getHostCard().isInstant() || this.getHostCard().hasKeyword(Keyword.FLASH)) {
return true;
}
diff --git a/forge-game/src/main/java/forge/game/card/CardLists.java b/forge-game/src/main/java/forge/game/card/CardLists.java
index 3689a2182d2..cbb9a147018 100644
--- a/forge-game/src/main/java/forge/game/card/CardLists.java
+++ b/forge-game/src/main/java/forge/game/card/CardLists.java
@@ -26,6 +26,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.collect.FCollectionView;
@@ -235,7 +236,11 @@ public class CardLists {
return CardLists.filter(cardList, CardPredicates.isTargetableBy(source));
}
- public static CardCollection getKeyword(Iterable cardList, String keyword) {
+ public static CardCollection getKeyword(Iterable cardList, final String keyword) {
+ return CardLists.filter(cardList, CardPredicates.hasKeyword(keyword));
+ }
+
+ public static CardCollection getKeyword(Iterable cardList, final Keyword keyword) {
return CardLists.filter(cardList, CardPredicates.hasKeyword(keyword));
}
@@ -243,6 +248,10 @@ public class CardLists {
return CardLists.filter(cardList, Predicates.not(CardPredicates.hasKeyword(keyword)));
}
+ public static CardCollection getNotKeyword(Iterable cardList, final Keyword keyword) {
+ return CardLists.filter(cardList, Predicates.not(CardPredicates.hasKeyword(keyword)));
+ }
+
public static int getAmountOfKeyword(final Iterable cardList, final String keyword) {
int nKeyword = 0;
for (final Card c : cardList) {
@@ -250,6 +259,13 @@ public class CardLists {
}
return nKeyword;
}
+ public static int getAmountOfKeyword(final Iterable cardList, final Keyword keyword) {
+ int nKeyword = 0;
+ for (final Card c : cardList) {
+ nKeyword += c.getAmountOfKeyword(keyword);
+ }
+ return nKeyword;
+ }
// cardType is like "Land" or "Goblin", returns a new CardCollection that is a
// subset of current CardList
public static CardCollection getNotType(Iterable cardList, String cardType) {
diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java
index d462bddcabb..b30734de635 100644
--- a/forge-game/src/main/java/forge/game/card/CardPredicates.java
+++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java
@@ -23,6 +23,7 @@ import com.google.common.base.Function;
import com.google.common.base.Predicate;
import forge.game.combat.CombatUtil;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -84,6 +85,15 @@ public final class CardPredicates {
};
}
+ public static final Predicate hasKeyword(final Keyword keyword) {
+ return new Predicate() {
+ @Override
+ public boolean apply(final Card c) {
+ return c.hasKeyword(keyword);
+ }
+ };
+ }
+
public static final Predicate containsKeyword(final String keyword) {
return new Predicate() {
@Override
diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java
index fb781195398..2703dd056d7 100644
--- a/forge-game/src/main/java/forge/game/card/CardState.java
+++ b/forge-game/src/main/java/forge/game/card/CardState.java
@@ -19,7 +19,6 @@ package forge.game.card;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.card.*;
import forge.card.mana.ManaCost;
@@ -28,6 +27,7 @@ import forge.game.CardTraitBase;
import forge.game.ForgeScript;
import forge.game.GameObject;
import forge.game.card.CardView.CardStateView;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
@@ -60,7 +60,7 @@ public class CardState extends GameObject {
private String imageKey = "";
private Map sVars = Maps.newTreeMap();
- private List cachedKeywords = Lists.newArrayList();
+ private KeywordCollection cachedKeywords = new KeywordCollection();
private CardRarity rarity = CardRarity.Unknown;
private String setCode = CardEdition.UNKNOWN.getCode();
@@ -167,12 +167,19 @@ public class CardState extends GameObject {
}
public final Collection getCachedKeywords() {
- return cachedKeywords;
+ return cachedKeywords.getValues();
}
- public final void setCachedKeywords(final Collection col) {
- cachedKeywords.clear();
- cachedKeywords.addAll(col);
+ public final Collection getCachedKeyword(final Keyword keyword) {
+ return cachedKeywords.getValues(keyword);
+ }
+
+ public final void setCachedKeywords(final KeywordCollection col) {
+ cachedKeywords = col;
+ }
+
+ public final boolean hasKeyword(Keyword key) {
+ return cachedKeywords.contains(key);
}
public final Collection getIntrinsicKeywords() {
diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java
index 90d0cd59bad..763268e813f 100644
--- a/forge-game/src/main/java/forge/game/card/CardView.java
+++ b/forge-game/src/main/java/forge/game/card/CardView.java
@@ -8,6 +8,7 @@ import forge.card.mana.ManaCost;
import forge.game.Direction;
import forge.game.GameEntityView;
import forge.game.combat.Combat;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerView;
import forge.game.zone.ZoneType;
@@ -978,11 +979,11 @@ public class CardView extends GameEntityView {
}
void updateKeywords(Card c, CardState state) {
c.updateKeywordsCache(state);
- set(TrackableProperty.HasDeathtouch, c.hasKeyword("Deathtouch", state));
- set(TrackableProperty.HasHaste, c.hasKeyword("Haste", state));
- set(TrackableProperty.HasInfect, c.hasKeyword("Infect", state));
- set(TrackableProperty.HasStorm, c.hasKeyword("Storm", state));
- set(TrackableProperty.HasTrample, c.hasKeyword("Trample", state));
+ set(TrackableProperty.HasDeathtouch, c.hasKeyword(Keyword.DEATHTOUCH, state));
+ set(TrackableProperty.HasHaste, c.hasKeyword(Keyword.HASTE, state));
+ set(TrackableProperty.HasInfect, c.hasKeyword(Keyword.INFECT, state));
+ set(TrackableProperty.HasStorm, c.hasKeyword(Keyword.STORM, state));
+ set(TrackableProperty.HasTrample, c.hasKeyword(Keyword.TRAMPLE, state));
set(TrackableProperty.BlockAdditional, c.getAmountOfKeyword("CARDNAME can block an additional creature each combat.", state));
updateAbilityText(c, state);
}
diff --git a/forge-game/src/main/java/forge/game/combat/AttackingBand.java b/forge-game/src/main/java/forge/game/combat/AttackingBand.java
index 6058db6db28..3158cbcea73 100644
--- a/forge-game/src/main/java/forge/game/combat/AttackingBand.java
+++ b/forge-game/src/main/java/forge/game/combat/AttackingBand.java
@@ -7,6 +7,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
+import forge.game.keyword.Keyword;
public class AttackingBand {
private CardCollection attackers = new CardCollection();
@@ -32,7 +33,7 @@ public class AttackingBand {
return false;
}
- int bandingCreatures = CardLists.getKeyword(band, "Banding").size();
+ int bandingCreatures = CardLists.getKeyword(band, Keyword.BANDING).size();
int neededBandingCreatures = shareDamage ? 1 : band.size() - 1;
if (neededBandingCreatures <= bandingCreatures) {
// For starting a band, only one can be non-Banding
diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java
index c3d8576a8bf..fb9c095306b 100644
--- a/forge-game/src/main/java/forge/game/combat/Combat.java
+++ b/forge-game/src/main/java/forge/game/combat/Combat.java
@@ -27,6 +27,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.trigger.TriggerType;
@@ -692,7 +693,7 @@ public class Combat {
continue;
}
- boolean trampler = attacker.hasKeyword("Trample");
+ boolean trampler = attacker.hasKeyword(Keyword.TRAMPLE);
orderedBlockers = blockersOrderedForDamageAssignment.get(attacker);
assignedDamage = true;
// If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender
diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java
index 1c3a7326463..388990b4ec1 100644
--- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java
+++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java
@@ -29,6 +29,7 @@ import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.card.*;
import forge.game.cost.Cost;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -658,7 +659,7 @@ public class CombatUtil {
return 0;
}
// TODO: remove CantBeBlockedByAmount LT2
- if (attacker.hasKeyword("CantBeBlockedByAmount LT2") || attacker.hasKeyword("Menace")) {
+ if (attacker.hasKeyword("CantBeBlockedByAmount LT2") || attacker.hasKeyword(Keyword.MENACE)) {
return 2;
} else if (attacker.hasKeyword("CantBeBlockedByAmount LT3")) {
return 3;
@@ -702,7 +703,7 @@ public class CombatUtil {
for (final Card attacker : attackers) {
if (CombatUtil.canBlock(attacker, blocker, combat)) {
boolean must = true;
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("Menace")) {
+ if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
final List possibleBlockers = Lists.newArrayList(defendersArmy);
possibleBlockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, possibleBlockers, combat)) {
@@ -806,7 +807,7 @@ public class CombatUtil {
for (final Card attacker : attackersWithLure) {
if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker)) {
boolean canBe = true;
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("Menace")) {
+ if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
blockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, blockers, combat)) {
@@ -824,7 +825,7 @@ public class CombatUtil {
if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker)
&& combat.isAttacking(attacker)) {
boolean canBe = true;
- if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("Menace")) {
+ if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
blockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, blockers, combat)) {
@@ -985,17 +986,17 @@ public class CombatUtil {
}
// rare case:
- if (blocker.hasKeyword("Shadow")
+ if (blocker.hasKeyword(Keyword.SHADOW)
&& blocker.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
return false;
}
- if (attacker.hasKeyword("Shadow") && !blocker.hasKeyword("Shadow")
+ if (attacker.hasKeyword(Keyword.SHADOW) && !blocker.hasKeyword(Keyword.SHADOW)
&& !blocker.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
return false;
}
- if (!attacker.hasKeyword("Shadow") && blocker.hasKeyword("Shadow")) {
+ if (!attacker.hasKeyword(Keyword.SHADOW) && blocker.hasKeyword(Keyword.SHADOW)) {
return false;
}
@@ -1003,7 +1004,7 @@ public class CombatUtil {
return false;
}
- if ((attacker.hasKeyword("Creatures with power greater than CARDNAME's power can't block it.") || attacker.hasKeyword("Skulk"))
+ if ((attacker.hasKeyword("Creatures with power greater than CARDNAME's power can't block it.") || attacker.hasKeyword(Keyword.SKULK))
&& attacker.getNetPower() < blocker.getNetPower()) {
return false;
}
@@ -1034,11 +1035,11 @@ public class CombatUtil {
}
}
- if (blocker.hasKeyword("CARDNAME can block only creatures with flying.") && !attacker.hasKeyword("Flying")) {
+ if (blocker.hasKeyword("CARDNAME can block only creatures with flying.") && !attacker.hasKeyword(Keyword.FLYING)) {
return false;
}
- if (attacker.hasKeyword("Flying") && !blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) {
+ if (attacker.hasKeyword(Keyword.FLYING) && !blocker.hasKeyword(Keyword.FLYING) && !blocker.hasKeyword(Keyword.REACH)) {
boolean stillblock = false;
for (KeywordInterface inst : blocker.getKeywords()) {
String k = inst.getOriginal();
@@ -1055,15 +1056,16 @@ public class CombatUtil {
}
}
- if (attacker.hasKeyword("Horsemanship") && !blocker.hasKeyword("Horsemanship")) {
+ if (attacker.hasKeyword(Keyword.HORSEMANSHIP) && !blocker.hasKeyword(Keyword.HORSEMANSHIP)) {
return false;
}
- if (attacker.hasKeyword("Fear") && !blocker.isArtifact() && !blocker.isBlack()) {
+ // color is hardcoded there
+ if (attacker.hasKeyword(Keyword.FEAR) && !blocker.isArtifact() && !blocker.isBlack()) {
return false;
}
- if (attacker.hasKeyword("Intimidate") && !blocker.isArtifact() && !blocker.sharesColorWith(attacker)) {
+ if (attacker.hasKeyword(Keyword.INTIMIDATE) && !blocker.isArtifact() && !blocker.sharesColorWith(attacker)) {
return false;
}
diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java
index 0e65b1a2295..82bfc0608db 100644
--- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java
+++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java
@@ -10,6 +10,7 @@ import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
@@ -210,7 +211,7 @@ public class CostAdjustment {
}
if (sa.isSpell()) {
- if (sa.getHostCard().hasKeyword("Delve")) {
+ if (sa.getHostCard().hasKeyword(Keyword.DELVE)) {
sa.getHostCard().clearDelved();
final CardCollection delved = new CardCollection();
@@ -235,10 +236,10 @@ public class CostAdjustment {
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
}
- if (sa.getHostCard().hasKeyword("Convoke")) {
+ if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) {
adjustCostByConvokeOrImprovise(cost, sa, false, test);
}
- if (sa.getHostCard().hasKeyword("Improvise")) {
+ if (sa.getHostCard().hasKeyword(Keyword.IMPROVISE)) {
adjustCostByConvokeOrImprovise(cost, sa, true, test);
}
} // isSpell
diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java
index 80393dc2186..f00b65cf76d 100644
--- a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java
+++ b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java
@@ -143,6 +143,10 @@ public class KeywordCollection implements Iterable, Serializable {
return map.values();
}
+ public Collection getValues(final Keyword keyword) {
+ return map.get(keyword);
+ }
+
@Override
public Iterator iterator() {
return new Iterator() {
diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
index 4ffc1d30099..a10ca915a34 100644
--- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
+++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
@@ -33,6 +33,7 @@ import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.Cost;
import forge.game.event.*;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.player.PlayerController.ManaPaymentPurpose;
@@ -507,7 +508,7 @@ public class PhaseHandler implements java.io.Serializable {
}
for (final Card attacker : combat.getAttackers()) {
- final boolean shouldTapForAttack = !attacker.hasKeyword("Vigilance") && !attacker.hasKeyword("Attacking doesn't cause CARDNAME to tap.");
+ final boolean shouldTapForAttack = !attacker.hasKeyword(Keyword.VIGILANCE) && !attacker.hasKeyword("Attacking doesn't cause CARDNAME to tap.");
if (shouldTapForAttack) {
// set tapped to true without firing triggers because it may affect propaganda costs
attacker.setTapped(true);
diff --git a/forge-game/src/main/java/forge/game/phase/Untap.java b/forge-game/src/main/java/forge/game/phase/Untap.java
index fdc08f31644..e06f7ad7252 100644
--- a/forge-game/src/main/java/forge/game/phase/Untap.java
+++ b/forge-game/src/main/java/forge/game/phase/Untap.java
@@ -36,6 +36,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
@@ -257,7 +258,7 @@ public class Untap extends Phase {
@Override
public boolean apply(final Card c) {
- return ((c.isPhasedOut() && c.isDirectlyPhasedOut()) || c.hasKeyword("Phasing"));
+ return ((c.isPhasedOut() && c.isDirectlyPhasedOut()) || c.hasKeyword(Keyword.PHASING));
}
});
@@ -268,7 +269,7 @@ public class Untap extends Phase {
for (final Card c : list) {
if (c.isPhasedOut()) {
c.phase();
- } else if (c.hasKeyword("Phasing")) {
+ } else if (c.hasKeyword(Keyword.PHASING)) {
// 702.23g If an object would simultaneously phase out directly
// and indirectly, it just phases out indirectly.
if (c.isAura()) {
diff --git a/forge-game/src/main/java/forge/game/player/AchievementTracker.java b/forge-game/src/main/java/forge/game/player/AchievementTracker.java
index 434a2467a1a..7f7d0d0d82e 100644
--- a/forge-game/src/main/java/forge/game/player/AchievementTracker.java
+++ b/forge-game/src/main/java/forge/game/player/AchievementTracker.java
@@ -5,6 +5,7 @@ import java.util.Set;
import forge.card.ColorSet;
import forge.game.card.Card;
+import forge.game.keyword.Keyword;
import forge.game.spellability.SpellAbility;
//class for storing information during a game that is used at the end of the game to determine achievements
@@ -28,7 +29,7 @@ public class AchievementTracker {
public void onSpellResolve(final SpellAbility spell) {
final Card card = spell.getHostCard();
- if (card.hasKeyword("Epic")) {
+ if (card.hasKeyword(Keyword.EPIC)) {
challengesCompleted.add("Epic");
}
}
diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java
index 8896f11b81a..685eb2f827b 100644
--- a/forge-game/src/main/java/forge/game/player/Player.java
+++ b/forge-game/src/main/java/forge/game/player/Player.java
@@ -30,6 +30,7 @@ import forge.game.ability.effects.DetachedCardEffect;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.event.*;
+import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInterface;
import forge.game.keyword.KeywordCollection.KeywordCollectionView;
@@ -529,7 +530,7 @@ public class Player extends GameEntity implements Comparable {
//String additionalLog = "";
source.addDealtDamageToPlayerThisTurn(getName(), amount);
- boolean infect = source.hasKeyword("Infect")
+ boolean infect = source.hasKeyword(Keyword.INFECT)
|| hasKeyword("All damage is dealt to you as though its source had infect.");
if (infect) {
@@ -1070,6 +1071,11 @@ public class Player extends GameEntity implements Comparable {
return keywords.contains(keyword);
}
+ @Override
+ public final boolean hasKeyword(final Keyword keyword) {
+ return keywords.contains(keyword);
+ }
+
private void updateKeywords() {
keywords.clear();
@@ -2773,4 +2779,8 @@ public class Player extends GameEntity implements Comparable {
this.updateZoneForView(com);
}
+
+ public final int countExaltedBonus() {
+ return CardLists.getAmountOfKeyword(this.getCardsIn(ZoneType.Battlefield), Keyword.EXALTED);
+ }
}
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
index 3c555c28624..c47dd86d932 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
@@ -86,7 +86,7 @@ public class ReplacementHandler {
// Round up Static replacement effects
game.forEachCardInGame(new Visitor() {
@Override
- public void visit(Card crd) {
+ public boolean visit(Card crd) {
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
// Use "CheckLKIZone" parameter to test for effects that care abut where the card was last (e.g. Kalitas, Traitor of Ghet
@@ -111,6 +111,7 @@ public class ReplacementHandler {
possibleReplacers.add(replacementEffect);
}
}
+ return true;
}
});
@@ -307,7 +308,7 @@ public class ReplacementHandler {
public void cleanUpTemporaryReplacements() {
game.forEachCardInGame(new Visitor() {
@Override
- public void visit(Card c) {
+ public boolean visit(Card c) {
for (int i = 0; i < c.getReplacementEffects().size(); i++) {
ReplacementEffect rep = c.getReplacementEffects().get(i);
if (rep.isTemporary()) {
@@ -315,14 +316,16 @@ public class ReplacementHandler {
i--;
}
}
+ return true;
}
});
game.forEachCardInGame(new Visitor() {
@Override
- public void visit(Card c) {
+ public boolean visit(Card c) {
for (int i = 0; i < c.getReplacementEffects().size(); i++) {
c.getReplacementEffects().get(i).setTemporarilySuppressed(false);
}
+ return true;
}
});
}
diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
index 103ea7aea66..8f9a469506d 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
@@ -35,6 +35,7 @@ import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.cost.CostRemoveCounter;
+import forge.game.keyword.Keyword;
import forge.game.mana.Mana;
import forge.game.player.Player;
import forge.game.staticability.StaticAbility;
@@ -1606,7 +1607,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public boolean tracksManaSpent() {
if (hostCard == null || hostCard.getRules() == null) { return false; }
- if (hostCard.hasKeyword("Sunburst")) {
+ if (hostCard.hasKeyword(Keyword.SUNBURST)) {
return true;
}
String text = hostCard.getRules().getOracleText();
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java
index e00ac10dfd6..cd25888a542 100644
--- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java
@@ -61,7 +61,7 @@ public class TriggerHandler {
public final void cleanUpTemporaryTriggers() {
game.forEachCardInGame(new Visitor() {
@Override
- public void visit(Card c) {
+ public boolean visit(Card c) {
boolean changed = false;
for (int i = 0; i < c.getTriggers().size(); i++) {
Trigger trigger = c.getTriggers().get(i);
@@ -74,11 +74,12 @@ public class TriggerHandler {
if (changed) {
c.updateStateForView();
}
+ return true;
}
});
game.forEachCardInGame(new Visitor() {
@Override
- public void visit(Card c) {
+ public boolean visit(Card c) {
boolean changed = false;
for (int i = 0; i < c.getTriggers().size(); i++) {
if (c.getTriggers().get(i).isSuppressed()) {
@@ -89,6 +90,7 @@ public class TriggerHandler {
if (changed) {
c.updateStateForView();
}
+ return true;
}
});
}
@@ -226,12 +228,13 @@ public class TriggerHandler {
activeTriggers.clear();
game.forEachCardInGame(new Visitor() {
@Override
- public void visit(Card c) {
+ public boolean visit(Card c) {
for (final Trigger t : c.getTriggers()) {
if (isTriggerActive(t)) {
activeTriggers.add(t);
}
}
+ return true;
}
});
}
diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java
index 73b06768b0d..6af1e1c31fa 100644
--- a/forge-game/src/main/java/forge/game/zone/MagicStack.java
+++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java
@@ -51,6 +51,7 @@ import forge.game.event.GameEventSpellAbilityCast;
import forge.game.event.GameEventSpellRemovedFromStack;
import forge.game.event.GameEventSpellResolved;
import forge.game.event.GameEventZone;
+import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerController.ManaPaymentPurpose;
import forge.game.replacement.ReplacementEffect;
@@ -119,7 +120,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable vBlockers = CardView.getCollection(blockers);
- if ((attacker.hasKeyword("Trample") && defender != null) || (blockers.size() > 1)) {
+ if ((attacker.hasKeyword(Keyword.TRAMPLE) && defender != null) || (blockers.size() > 1)) {
final CardView vAttacker = CardView.get(attacker);
final GameEntityView vDefender = GameEntityView.get(defender);
final Map result = getGui().assignDamage(vAttacker, vBlockers, damageDealt,
@@ -2296,7 +2296,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
if (forgeCard.isPermanent() && !forgeCard.isAura()) {
if (forgeCard.isCreature()) {
if (!repeatLast) {
- if (forgeCard.hasKeyword("Haste")) {
+ if (forgeCard.hasKeyword(Keyword.HASTE)) {
lastSummoningSickness = true;
} else {
lastSummoningSickness = getGui().confirm(forgeCard.getView(),