KeywordEnum: replace more String Keywords checks with Enums

This commit is contained in:
Hanmac
2018-05-10 11:53:41 +02:00
parent 91d80f49f4
commit 058aabb666
51 changed files with 553 additions and 447 deletions

View File

@@ -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)) {
@@ -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,8 +733,9 @@ public class AiAttackController {
// Exalted
if (combat.getAttackers().isEmpty()) {
boolean exalted = false;
int exaltedCount = 0;
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;
@@ -740,12 +745,6 @@ public class AiAttackController {
exalted = true;
break;
}
if (c.hasKeyword("Exalted")) {
exaltedCount++;
if (exaltedCount > 2) {
exalted = true;
break;
}
}
}
if (exalted) {
@@ -1061,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;
}
@@ -1092,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.
@@ -1144,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
@@ -1160,15 +1159,12 @@ 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")) {
if (defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT)
|| defender.hasKeyword(Keyword.LIFELINK)) {
canKillAllDangerous = false;
break;
// 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
if (canKillAllDangerous) {
@@ -1191,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;
@@ -1277,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;
}

View File

@@ -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;
@@ -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,10 +232,9 @@ public class AiBlockController {
// 3.Blockers that can destroy the attacker and have an upside when dying
killingBlockers = getKillingBlockers(combat, attacker, blockers);
for (Card b : killingBlockers) {
if ((b.hasKeyword("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)
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;
@@ -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,8 +303,8 @@ public class AiBlockController {
final List<Card> 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)
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)) {
@@ -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<Card> chumpBlockers;
List<Card> tramplingAttackers = CardLists.getKeyword(attackers, "Trample");
List<Card> 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;
@@ -789,15 +788,15 @@ public class AiBlockController {
}
}
// don't try to kill what can't be killed
if (attacker.hasKeyword("indestructible") || ComputerUtil.canRegenerate(ai, attacker)) {
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;
}
@@ -1025,22 +1024,26 @@ public class AiBlockController {
lifeInDanger = false;
}
// if life is still in danger
// Reinforce blockers blocking attackers with trample if life is still
// 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
// 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 ==
// == 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
clearBlockers(combat, possibleBlockers); // reset every block
// assignment
makeTradeBlocks(combat); // choose necessary trade blocks
// if life is in danger
makeGoodBlocks(combat);
@@ -1061,9 +1064,11 @@ public class AiBlockController {
reinforceBlockersToKill(combat);
}
// == 3. If the AI life would be in serious danger make an even safer approach ==
// == 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
clearBlockers(combat, possibleBlockers); // reset every block
// assignment
makeChumpBlocks(combat); // choose chump blocks
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeTradeBlocks(combat); // choose necessary trade
@@ -1078,7 +1083,8 @@ public class AiBlockController {
reinforceBlockersAgainstTrample(combat);
}
makeGangBlocks(combat);
// Support blockers not destroying the attacker with more blockers
// Support blockers not destroying the attacker with more
// blockers
// to try to kill the attacker
reinforceBlockersToKill(combat);
}
@@ -1167,7 +1173,8 @@ 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) {
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()
@@ -1182,17 +1189,21 @@ 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)) {
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
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;
}

View File

@@ -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;
}

View File

@@ -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<String> 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;
}

View File

@@ -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<Card> 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;

View File

@@ -202,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;
@@ -303,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);
}
}
@@ -332,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<Card> attackers = combat.getAttackersOf(ai);
@@ -578,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);
}
@@ -592,7 +597,7 @@ 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;
}
@@ -923,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();
}
@@ -1266,7 +1271,7 @@ public class ComputerUtilCombat {
if (ComputerUtilCombat.dealsFirstStrikeDamage(blocker, withoutAbilities, combat)
&& (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT))
&& !ComputerUtilCombat.dealsFirstStrikeDamage(attacker, withoutAbilities, combat)
&& !attacker.hasKeyword("CARDNAME can't have counters put on it.")) {
&& !attacker.canReceiveCounters(CounterType.M1M1)) {
power -= blocker.getNetCombatDamage();
}
theTriggers.addAll(blocker.getTriggers());
@@ -1493,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
@@ -1646,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;
}
@@ -1711,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;
}
@@ -1829,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;
}
@@ -1839,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;
}
@@ -1855,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) {
@@ -2011,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;
}
@@ -2065,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;
}
@@ -2075,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;
}
@@ -2090,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) {
@@ -2137,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();
@@ -2239,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) {
@@ -2406,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;
}
@@ -2542,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);

View File

@@ -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,7 +447,7 @@ 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)) {

View File

@@ -84,7 +84,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
if (power > 0) {
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(Keyword.DEATHTOUCH)) {
@@ -116,7 +116,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
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(Keyword.OUTLAST)) {
@@ -145,7 +145,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
} else if (c.hasKeyword(Keyword.SHROUD)) {
value += addValue(30, "shroud");
}
if (c.hasStartOfKeyword("Protection")) {
if (c.hasKeyword(Keyword.PROTECTION)) {
value += addValue(20, "protection");
}
@@ -186,7 +186,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
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");
}

View File

@@ -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<Integer, Integer> 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;
}

View File

@@ -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<Card>() {
@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<Card> evenBetterList = CardLists.filter(betterList, new Predicate<Card>() {
@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<Card>() {
@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<Card>() {
@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")) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<Card> killable = CardLists.getNotKeyword(CardLists.filterToughness(list, amount), "Undying");
final List<Card> killable = CardLists.getNotKeyword(CardLists.filterToughness(list, amount), Keyword.UNDYING);
if (killable.size() > 0) {
choice = ComputerUtilCard.getBestCreatureAI(killable);
} else {

View File

@@ -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;

View File

@@ -15,6 +15,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;
@@ -154,7 +155,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<Card>() {
@Override
@@ -339,14 +340,8 @@ public class CountersPutAi extends SpellAbilityAi {
if ("Polukranos".equals(sa.getParam("AILogic"))) {
CardCollection humCreatures = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
final CardCollection targets = CardLists.filter(humCreatures, new Predicate<Card>() {
@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) {
@@ -792,12 +787,12 @@ public class CountersPutAi extends SpellAbilityAi {
final List<Card> 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<Card> threatening = CardLists.filter(creats, new Predicate<Card>() {
@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()) {
@@ -814,7 +809,7 @@ public class CountersPutAi extends SpellAbilityAi {
List<Card> canBlock = CardLists.filter(creats, new Predicate<Card>() {
@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()) {
@@ -907,7 +902,7 @@ public class CountersPutAi extends SpellAbilityAi {
final CardCollection persist = CardLists.filter(filtered, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
if (!input.hasKeyword("Persist"))
if (!input.hasKeyword(Keyword.PERSIST))
return false;
return input.getCounters(CounterType.M1M1) <= amount;
}
@@ -920,7 +915,7 @@ public class CountersPutAi extends SpellAbilityAi {
final CardCollection undying = CardLists.filter(filtered, new Predicate<Card>() {
@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;
}
@@ -945,7 +940,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) {

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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,7 +25,8 @@ 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");
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")) {
@@ -33,6 +35,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
}
}
}
}
if ("SelfDamage".equals(sa.getParam("AILogic"))) {
if (comp.getLife() * 0.75 < enemy.getLife()) {
if (!lifelink) {

View File

@@ -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;

View File

@@ -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"

View File

@@ -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);

View File

@@ -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<Card> predicate = new Predicate<Card>() {
@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);
}
};

View File

@@ -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<Card>() {
@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);
}
});

View File

@@ -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;
@@ -246,15 +247,17 @@ 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)) {
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) {
if (fighter.hasKeyword(Keyword.DEATHTOUCH)
|| ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
return true;
}
return false;

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

@@ -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)) {

View File

@@ -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<Card> flyingOrReach = Predicates.or(CardPredicates.hasKeyword("Flying"), CardPredicates.hasKeyword("Reach"));
Predicate<Card> 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,7 +304,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
} else if (keyword.equals("First Strike")) {
if (card.hasKeyword("Double Strike")) {
if (card.hasKeyword(Keyword.DOUBLE_STRIKE)) {
return false;
}
if (combat != null && combat.isBlocked(card) && !combat.getBlockers(card).isEmpty()) {
@@ -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

View File

@@ -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<Card>() {
@@ -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

View File

@@ -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;
}

View File

@@ -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;
@@ -201,7 +202,8 @@ public class TokenAi extends SpellAbilityAi {
sa.getTargets().add(ai);
} else {
// Flash Foliage
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
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<Card>() {
@@ -213,7 +215,7 @@ public class TokenAi extends SpellAbilityAi {
if (!betterList.isEmpty()) {
list = betterList;
}
betterList = CardLists.getNotKeyword(list, "Trample");
betterList = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
if (!betterList.isEmpty()) {
list = betterList;
}
@@ -222,7 +224,6 @@ public class TokenAi extends SpellAbilityAi {
} else {
return false;
}
}
}
}

View File

@@ -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<SpellAbility> candidateSAs2 = new ArrayList<SpellAbility>();
for (SpellAbility sa : candidateSAs) {
if (!isSorcerySpeed(sa)) {
if (!isSorcerySpeed(sa, player)) {
System.err.println("Not sorcery: " + sa);
candidateSAs2.add(sa);
}

View File

@@ -1,11 +1,22 @@
package forge.util;
public abstract class Visitor<T> {
/**
* 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;
}
}

View File

@@ -527,25 +527,57 @@ public class Game {
return cards;
}
private static class CardStateVisitor extends Visitor<Card> {
Card found = null;
Card old = null;
private CardStateVisitor(final Card card) {
this.old = 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) {
for (final Card c : getCardsInGame()) {
if (card.equals(c)) {
return c;
}
}
return 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<Card> 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());
}

View File

@@ -236,7 +236,11 @@ public class CardLists {
return CardLists.filter(cardList, CardPredicates.isTargetableBy(source));
}
public static CardCollection getKeyword(Iterable<Card> cardList, String keyword) {
public static CardCollection getKeyword(Iterable<Card> cardList, final String keyword) {
return CardLists.filter(cardList, CardPredicates.hasKeyword(keyword));
}
public static CardCollection getKeyword(Iterable<Card> cardList, final Keyword keyword) {
return CardLists.filter(cardList, CardPredicates.hasKeyword(keyword));
}
@@ -244,6 +248,10 @@ public class CardLists {
return CardLists.filter(cardList, Predicates.not(CardPredicates.hasKeyword(keyword)));
}
public static CardCollection getNotKeyword(Iterable<Card> cardList, final Keyword keyword) {
return CardLists.filter(cardList, Predicates.not(CardPredicates.hasKeyword(keyword)));
}
public static int getAmountOfKeyword(final Iterable<Card> cardList, final String keyword) {
int nKeyword = 0;
for (final Card c : cardList) {

View File

@@ -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<Card> hasKeyword(final Keyword keyword) {
return new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return c.hasKeyword(keyword);
}
};
}
public static final Predicate<Card> containsKeyword(final String keyword) {
return new Predicate<Card>() {
@Override

View File

@@ -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);
}

View File

@@ -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

View File

@@ -659,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;
@@ -703,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<Card> possibleBlockers = Lists.newArrayList(defendersArmy);
possibleBlockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, possibleBlockers, combat)) {
@@ -807,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<Card> blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
blockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, blockers, combat)) {
@@ -825,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<Card> blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
blockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, blockers, combat)) {
@@ -1004,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;
}

View File

@@ -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);

View File

@@ -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()) {

View File

@@ -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");
}
}

View File

@@ -530,7 +530,7 @@ public class Player extends GameEntity implements Comparable<Player> {
//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) {

View File

@@ -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();

View File

@@ -22,6 +22,7 @@ import forge.StaticData;
import forge.assets.FSkinProp;
import forge.game.card.Card;
import forge.game.card.CardView.CardStateView;
import forge.game.keyword.Keyword;
import forge.gui.SOverlayUtils;
import forge.item.PaperCard;
import forge.toolbox.FOverlay;
@@ -230,7 +231,7 @@ public enum CardZoomer {
if (cardName.isEmpty()) { cardName = thisCard.getCard().getAlternateState().getName(); }
PaperCard pc = StaticData.instance().getCommonCards().getCard(cardName);
boolean isAftermath = pc != null && Card.getCardForUi(pc).hasKeyword("Aftermath");
boolean isAftermath = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH);
return thisCard.getCard().isFaceDown() || isSplitRotated ? 0 : isAftermath ? 270 : 90; // rotate Aftermath splits the other way to correctly show the right split (graveyard) half
}

View File

@@ -25,6 +25,7 @@ import forge.card.mana.ManaCost;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView;
import forge.game.keyword.Keyword;
import forge.game.card.CounterType;
import forge.gui.CardContainer;
import forge.item.PaperCard;
@@ -434,7 +435,7 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
} else {
if (!card.isFaceDown()) { // no need to draw mana symbols on face down split cards (e.g. manifested)
PaperCard pc = StaticData.instance().getCommonCards().getCard(card.getName());
int ofs = pc != null && Card.getCardForUi(pc).hasKeyword("Aftermath") ? -12 : 12;
int ofs = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH) ? -12 : 12;
drawManaCost(g, card.getCurrentState().getManaCost(), ofs);
drawManaCost(g, card.getAlternateState().getManaCost(), -ofs);

View File

@@ -25,6 +25,7 @@ import forge.card.mana.ManaCost;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView;
import forge.game.keyword.Keyword;
import forge.game.card.CounterType;
import forge.item.IPaperCard;
import forge.item.PaperCard;
@@ -417,7 +418,7 @@ public class CardRenderer {
float dy = manaSymbolSize / 2 + Utils.scale(5);
PaperCard pc = StaticData.instance().getCommonCards().getCard(card.getName());
if (Card.getCardForUi(pc).hasKeyword("Aftermath")){
if (Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH)){
dy *= -1; // flip card costs for Aftermath cards
}

View File

@@ -26,6 +26,7 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.combat.AttackingBand;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerView;
import forge.game.zone.ZoneType;
@@ -313,7 +314,7 @@ public class InputAttack extends InputSyncronizedBase {
private boolean isBandingPossible() {
final CardCollectionView possibleAttackers = playerAttacks.getCardsIn(ZoneType.Battlefield);
for (final Card c : possibleAttackers) {
if ((c.hasKeyword("Banding") || c.hasStartOfKeyword("Bands with Other")) &&
if ((c.hasKeyword(Keyword.BANDING) || c.hasStartOfKeyword("Bands with Other")) &&
CombatUtil.canAttack(c, currentDefender)) {
return true;
}

View File

@@ -280,7 +280,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
map.put(null, damageDealt);
} else {
final List<CardView> 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<CardView, Integer> 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(),