Merge branch 'hasKeywordEnum' into 'master'

Card: add Keyword methods that work with Keyword Enum

See merge request core-developers/forge!535
This commit is contained in:
Hans Mackowiak
2018-05-11 11:34:15 +00:00
64 changed files with 802 additions and 611 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)) {
@@ -358,7 +362,7 @@ public class AiAttackController {
// In addition, if the computer guesses it needs no blockers, make sure
// that
// it won't be surprised by Exalted
final int humanExaltedBonus = countExaltedBonus(opp);
final int humanExaltedBonus = opp.countExaltedBonus();
if (humanExaltedBonus > 0) {
final boolean finestHour = opp.isCardInPlay("Finest Hour");
@@ -543,7 +547,7 @@ public class AiAttackController {
int trampleDamage = 0;
for (Card attacker : blockedAttackers) {
if (attacker.hasKeyword("Trample")) {
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
int damage = ComputerUtilCombat.getAttack(attacker);
for (Card blocker : this.blockers) {
if (CombatUtil.canBlock(attacker, blocker)) {
@@ -729,20 +733,15 @@ public class AiAttackController {
// Exalted
if (combat.getAttackers().isEmpty()) {
boolean exalted = false;
int exaltedCount = 0;
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
if (c.getName().equals("Rafiq of the Many") || c.getName().equals("Battlegrace Angel")) {
exalted = true;
break;
}
if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) {
exalted = true;
break;
}
if (c.hasKeyword("Exalted")) {
exaltedCount++;
if (exaltedCount > 2) {
boolean exalted = ai.countExaltedBonus() > 2;
if (!exalted) {
for (Card c : ai.getCardsIn(ZoneType.Battlefield)) {
if (c.getName().equals("Rafiq of the Many") || c.getName().equals("Battlegrace Angel")) {
exalted = true;
break;
}
if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) {
exalted = true;
break;
}
@@ -1049,24 +1048,6 @@ public class AiAttackController {
}
} // getAttackers()
/**
* <p>
* countExaltedBonus.
* </p>
*
* @param player
* a {@link forge.game.player.Player} object.
* @return a int.
*/
public final static int countExaltedBonus(final Player player) {
int bonus = 0;
for (Card c : player.getCardsIn(ZoneType.Battlefield)) {
bonus += c.getAmountOfKeyword("Exalted");
}
return bonus;
}
/**
* <p>
* getAttack.
@@ -1079,7 +1060,7 @@ public class AiAttackController {
public final static int getAttack(final Card c) {
int n = c.getNetCombatDamage();
if (c.hasKeyword("Double Strike")) {
if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
n *= 2;
}
@@ -1110,7 +1091,7 @@ public class AiAttackController {
// Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking?
if ((attacker.hasSVar("NonCombatPriority"))
&& (!attacker.hasKeyword("Vigilance"))) {
&& (!attacker.hasKeyword(Keyword.VIGILANCE))) {
// For each level of priority, enemy has to have life as much as the creature's power
// so a priority of 4 means the creature will not attack unless it can defeat that player in 4 successful attacks.
// the lower the priroity, the less willing the AI is to use the creature for attacking.
@@ -1162,7 +1143,7 @@ public class AiAttackController {
&& CombatUtil.canBlock(attacker, defender)) {
numberOfPossibleBlockers += 1;
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
&& !(attacker.hasKeyword("Undying") && attacker.getCounters(CounterType.P1P1) == 0)) {
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.P1P1) == 0)) {
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
// see if the defending creature is of higher or lower
// value. We don't want to attack only to lose value
@@ -1178,14 +1159,11 @@ public class AiAttackController {
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) {
canKillAllDangerous = false;
} else {
for (KeywordInterface inst : defender.getKeywords()) {
String keyword = inst.getOriginal();
if (keyword.equals("Wither") || keyword.equals("Infect") || keyword.equals("Lifelink")) {
canKillAllDangerous = false;
break;
// there is a creature that can survive an attack from this creature
// and combat will have negative effects
}
if (defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT)
|| defender.hasKeyword(Keyword.LIFELINK)) {
canKillAllDangerous = false;
// there is a creature that can survive an attack from this creature
// and combat will have negative effects
}
// Check if maybe we are too reckless in adding this attacker
@@ -1209,7 +1187,7 @@ public class AiAttackController {
}
}
if (!attacker.hasKeyword("Vigilance") && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
if (!attacker.hasKeyword(Keyword.VIGILANCE) && ComputerUtilCard.canBeKilledByRoyalAssassin(ai, attacker)) {
canKillAllDangerous = false;
canBeKilled = true;
canBeKilledByOne = true;
@@ -1295,7 +1273,7 @@ public class AiAttackController {
// creature would leave the battlefield
// no pain in exerting it
shouldExert = true;
} else if (c.hasKeyword("Vigilance")) {
} else if (c.hasKeyword(Keyword.VIGILANCE)) {
// Free exert - why not?
shouldExert = true;
}

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;
@@ -117,11 +118,11 @@ public class AiBlockController {
// If I don't have any planeswalkers then sorting doesn't really matter
if (defenders.size() == 1) {
final CardCollection attackers = combat.getAttackersOf(defenders.get(0));
final CardCollection attackers = combat.getAttackersOf(defenders.get(0));
// Begin with the attackers that pose the biggest threat
ComputerUtilCard.sortByEvaluateCreature(attackers);
CardLists.sortByPowerDesc(attackers);
//move cards like Phage the Untouchable to the front
//move cards like Phage the Untouchable to the front
Collections.sort(attackers, new Comparator<Card>() {
@Override
public int compare(final Card o1, final Card o2) {
@@ -142,16 +143,16 @@ public class AiBlockController {
// defend planeswalkers with more loyalty before planeswalkers with less loyalty
// if planeswalker will be too difficult to defend don't even bother
for (GameEntity defender : defenders) {
if (defender instanceof Card) {
final CardCollection attackers = combat.getAttackersOf(defender);
// Begin with the attackers that pose the biggest threat
CardLists.sortByPowerDesc(attackers);
for (final Card c : attackers) {
sortedAttackers.add(c);
}
} else if (defender instanceof Player && defender.equals(ai)){
firstAttacker = combat.getAttackersOf(defender);
}
if (defender instanceof Card) {
final CardCollection attackers = combat.getAttackersOf(defender);
// Begin with the attackers that pose the biggest threat
CardLists.sortByPowerDesc(attackers);
for (final Card c : attackers) {
sortedAttackers.add(c);
}
} else if (defender instanceof Player && defender.equals(ai)) {
firstAttacker = combat.getAttackersOf(defender);
}
}
if (bLifeInDanger) {
@@ -180,7 +181,7 @@ public class AiBlockController {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|| attacker.hasKeyword("Menace")) {
|| attacker.hasKeyword(Keyword.MENACE)) {
continue;
}
@@ -204,11 +205,11 @@ public class AiBlockController {
&& !ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers);
// check whether it's better to block a creature without trample to absorb more damage
if (attacker.hasKeyword("Trample")) {
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
boolean doNotBlock = false;
for (Card other : attackersLeft) {
if (other.equals(attacker) || !CombatUtil.canBlock(other, blocker)
|| other.hasKeyword("Trample")
|| other.hasKeyword(Keyword.TRAMPLE)
|| ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
|| ComputerUtilCombat.canDestroyBlocker(ai, blocker, other, combat, false)
|| other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
@@ -231,11 +232,10 @@ public class AiBlockController {
// 3.Blockers that can destroy the attacker and have an upside when dying
killingBlockers = getKillingBlockers(combat, attacker, blockers);
for (Card b : killingBlockers) {
if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0)
|| b.hasSVar("SacMe")
|| (b.hasStartOfKeyword("Vanishing") && b.getCounters(CounterType.TIME) == 1)
|| (b.hasStartOfKeyword("Fading") && b.getCounters(CounterType.FADE) == 0)
|| b.hasSVar("EndOfTurnLeavePlay")) {
if ((b.hasKeyword(Keyword.UNDYING) && b.getCounters(CounterType.P1P1) == 0) || b.hasSVar("SacMe")
|| (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|| b.hasSVar("EndOfTurnLeavePlay")) {
blocker = b;
break;
}
@@ -245,7 +245,7 @@ public class AiBlockController {
if (b.hasSVar("SacMe") && Integer.parseInt(b.getSVar("SacMe")) > 3) {
blocker = b;
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
blockedButUnkilled.add(attacker);
blockedButUnkilled.add(attacker);
}
break;
}
@@ -293,8 +293,7 @@ public class AiBlockController {
// 6. Blockers that don't survive until the next turn anyway
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("Menace")
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
}
@@ -304,12 +303,12 @@ public class AiBlockController {
final List<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)
|| b.hasSVar("EndOfTurnLeavePlay")) {
if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|| b.hasSVar("EndOfTurnLeavePlay")) {
blocker = b;
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
blockedButUnkilled.add(attacker);
blockedButUnkilled.add(attacker);
}
break;
}
@@ -531,7 +530,7 @@ public class AiBlockController {
// Try to block a Menace attacker with two blockers, neither of which will die
for (final Card attacker : attackersLeft) {
if (!attacker.hasKeyword("Menace") && !attacker.hasStartOfKeyword("CantBeBlockedByAmount LT2")) {
if (!attacker.hasKeyword(Keyword.MENACE) && !attacker.hasStartOfKeyword("CantBeBlockedByAmount LT2")) {
continue;
}
@@ -590,7 +589,7 @@ public class AiBlockController {
for (final Card attacker : attackersLeft) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("Menace")
|| attacker.hasKeyword(Keyword.MENACE)
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
}
@@ -645,7 +644,7 @@ public class AiBlockController {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
|| attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")
|| attacker.hasKeyword("Menace")
|| attacker.hasKeyword(Keyword.MENACE)
|| ComputerUtilCombat.attackerHasThreateningAfflict(attacker, ai)) {
attackers.remove(0);
makeChumpBlocks(combat, attackers);
@@ -657,7 +656,7 @@ public class AiBlockController {
final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers);
// check if it's better to block a creature with lower power and without trample
if (attacker.hasKeyword("Trample")) {
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
final int damageAbsorbed = blocker.getLethalDamage();
if (attacker.getNetCombatDamage() > damageAbsorbed) {
for (Card other : attackers) {
@@ -665,7 +664,7 @@ public class AiBlockController {
continue;
}
if (other.getNetCombatDamage() >= damageAbsorbed
&& !other.hasKeyword("Trample")
&& !other.hasKeyword(Keyword.TRAMPLE)
&& !other.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")
&& !ComputerUtilCombat.attackerHasThreateningAfflict(other, ai)
&& CombatUtil.canBlock(other, blocker, combat)) {
@@ -696,7 +695,7 @@ public class AiBlockController {
for (final Card attacker : currentAttackers) {
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")
&& !attacker.hasKeyword("Menace")
&& !attacker.hasKeyword(Keyword.MENACE)
&& !attacker.hasKeyword("CARDNAME can't be blocked unless all creatures defending player controls block it.")) {
continue;
}
@@ -729,7 +728,7 @@ public class AiBlockController {
List<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;
@@ -775,29 +774,29 @@ public class AiBlockController {
// Try to use safe blockers first
if (blockers.size() > 0) {
safeBlockers = getSafeBlockers(combat, attacker, blockers);
for (final Card blocker : safeBlockers) {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
// Add an additional blocker if the current blockers are not
// enough and the new one would deal additional damage
if (damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))
&& ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0
&& CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker);
}
blockers.remove(blocker); // Don't check them again next
}
for (final Card blocker : safeBlockers) {
final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false);
// Add an additional blocker if the current blockers are not
// enough and the new one would deal additional damage
if (damageNeeded > ComputerUtilCombat.totalDamageOfBlockers(attacker, combat.getBlockers(attacker))
&& ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker) > 0
&& CombatUtil.canBlock(attacker, blocker, combat)) {
combat.addBlocker(attacker, blocker);
}
blockers.remove(blocker); // Don't check them again next
}
}
// don't try to kill what can't be killed
if (attacker.hasKeyword("indestructible") || ComputerUtil.canRegenerate(ai, attacker)) {
continue;
if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(ai, attacker)) {
continue;
}
// Try to add blockers that could be destroyed, but are worth less than the attacker
// Don't use blockers without First Strike or Double Strike if attacker has it
if (ComputerUtilCombat.dealsFirstStrikeDamage(attacker, false, combat)) {
safeBlockers = CardLists.getKeyword(blockers, "First Strike");
safeBlockers.addAll(CardLists.getKeyword(blockers, "Double Strike"));
safeBlockers = CardLists.getKeyword(blockers, Keyword.FIRST_STRIKE);
safeBlockers.addAll(CardLists.getKeyword(blockers, Keyword.DOUBLE_STRIKE));
} else {
safeBlockers = new ArrayList<>(blockers);
}
@@ -870,7 +869,7 @@ public class AiBlockController {
for (final Card attacker : attackers) {
GameEntity def = combat.getDefenderByAttacker(attacker);
if (def instanceof Card && threatenedPWs.contains((Card) def)) {
if (attacker.hasKeyword("Trample")) {
if (attacker.hasKeyword(Keyword.TRAMPLE)) {
// don't bother trying to chump a trampling creature
continue;
}
@@ -985,7 +984,7 @@ public class AiBlockController {
diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade
if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) {
diff = 0;
diff = 0;
}
// remove all attackers that can't be blocked anyway
@@ -1015,73 +1014,80 @@ public class AiBlockController {
// When the AI holds some Fog effect, don't bother about lifeInDanger
if (!ComputerUtil.hasAFogEffect(ai)) {
lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
makeTradeBlocks(combat); // choose necessary trade blocks
// if life is still in danger
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeChumpBlocks(combat); // choose necessary chump blocks
} else {
lifeInDanger = false;
// if life is still in danger
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeChumpBlocks(combat); // choose necessary chump blocks
} else {
lifeInDanger = false;
}
// if life is still in danger
// Reinforce blockers blocking attackers with trample if life is still
// in danger
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersAgainstTrample(combat);
} else {
lifeInDanger = false;
// if life is still in danger
// Reinforce blockers blocking attackers with trample if life is
// still
// in danger
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersAgainstTrample(combat);
} else {
lifeInDanger = false;
}
// Support blockers not destroying the attacker with more blockers
// to
// try to kill the attacker
if (!lifeInDanger) {
reinforceBlockersToKill(combat);
}
// Support blockers not destroying the attacker with more blockers to
// try to kill the attacker
if (!lifeInDanger) {
reinforceBlockersToKill(combat);
}
// == 2. If the AI life would still be in danger make a safer approach ==
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
clearBlockers(combat, possibleBlockers); // reset every block assignment
makeTradeBlocks(combat); // choose necessary trade blocks
// if life is in danger
makeGoodBlocks(combat);
// choose necessary chump blocks if life is still in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeChumpBlocks(combat);
} else {
lifeInDanger = false;
}
// Reinforce blockers blocking attackers with trample if life is
// still in danger
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersAgainstTrample(combat);
} else {
lifeInDanger = false;
}
makeGangBlocks(combat);
reinforceBlockersToKill(combat);
}
// == 2. If the AI life would still be in danger make a safer
// approach ==
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
clearBlockers(combat, possibleBlockers); // reset every block
// assignment
makeTradeBlocks(combat); // choose necessary trade blocks
// if life is in danger
makeGoodBlocks(combat);
// choose necessary chump blocks if life is still in danger
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeChumpBlocks(combat);
} else {
lifeInDanger = false;
}
// Reinforce blockers blocking attackers with trample if life is
// still in danger
if (lifeInDanger && ComputerUtilCombat.lifeInDanger(ai, combat)) {
reinforceBlockersAgainstTrample(combat);
} else {
lifeInDanger = false;
}
makeGangBlocks(combat);
reinforceBlockersToKill(combat);
}
// == 3. If the AI life would be in serious danger make an even safer approach ==
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
clearBlockers(combat, possibleBlockers); // reset every block assignment
makeChumpBlocks(combat); // choose chump blocks
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeTradeBlocks(combat); // choose necessary trade
}
// == 3. If the AI life would be in serious danger make an even
// safer approach ==
if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
clearBlockers(combat, possibleBlockers); // reset every block
// assignment
makeChumpBlocks(combat); // choose chump blocks
if (ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeTradeBlocks(combat); // choose necessary trade
}
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeGoodBlocks(combat);
}
// Reinforce blockers blocking attackers with trample if life is
// still in danger
else {
reinforceBlockersAgainstTrample(combat);
}
makeGangBlocks(combat);
// Support blockers not destroying the attacker with more blockers
// to try to kill the attacker
reinforceBlockersToKill(combat);
}
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
makeGoodBlocks(combat);
}
// Reinforce blockers blocking attackers with trample if life is
// still in danger
else {
reinforceBlockersAgainstTrample(combat);
}
makeGangBlocks(combat);
// Support blockers not destroying the attacker with more
// blockers
// to try to kill the attacker
reinforceBlockersToKill(combat);
}
}
// assign blockers that have to block
@@ -1167,10 +1173,11 @@ public class AiBlockController {
* Orders a blocker that put onto the battlefield blocking. Depends heavily
* on the implementation of orderBlockers().
*/
public static CardCollection orderBlocker(final Card attacker, final Card blocker, final CardCollection oldBlockers) {
// add blocker to existing ordering
// sort by evaluate, then insert it appropriately
// relies on current implementation of orderBlockers()
public static CardCollection orderBlocker(final Card attacker, final Card blocker,
final CardCollection oldBlockers) {
// add blocker to existing ordering
// sort by evaluate, then insert it appropriately
// relies on current implementation of orderBlockers()
final CardCollection allBlockers = new CardCollection(oldBlockers);
allBlockers.add(blocker);
ComputerUtilCard.sortByEvaluateCreature(allBlockers);
@@ -1182,24 +1189,28 @@ public class AiBlockController {
boolean newBlockerIsAdded = false;
// The new blocker comes right after this one
final Card newBlockerRightAfter = (newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1));
if (newBlockerRightAfter == null && damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
result.add(blocker);
newBlockerIsAdded = true;
if (newBlockerRightAfter == null
&& damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
result.add(blocker);
newBlockerIsAdded = true;
}
// Don't bother to keep damage up-to-date after the new blocker is added, as we can't modify the order of the other cards anyway
// Don't bother to keep damage up-to-date after the new blocker is
// added, as we can't modify the order of the other cards anyway
for (final Card c : oldBlockers) {
final int lethal = ComputerUtilCombat.getEnoughDamageToKill(c, damage, attacker, true);
damage -= lethal;
result.add(c);
if (!newBlockerIsAdded && c == newBlockerRightAfter && damage <= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
// If blocker is right after this card in priority and we have sufficient damage to kill it, add it here
result.add(blocker);
newBlockerIsAdded = true;
}
final int lethal = ComputerUtilCombat.getEnoughDamageToKill(c, damage, attacker, true);
damage -= lethal;
result.add(c);
if (!newBlockerIsAdded && c == newBlockerRightAfter
&& damage <= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
// If blocker is right after this card in priority and we have
// sufficient damage to kill it, add it here
result.add(blocker);
newBlockerIsAdded = true;
}
}
// We don't have sufficient damage, just add it at the end!
if (!newBlockerIsAdded) {
result.add(blocker);
result.add(blocker);
}
return result;

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

@@ -36,6 +36,7 @@ import forge.game.card.*;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.cost.CostPayment;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.phase.Untap;
import forge.game.player.Player;
@@ -201,10 +202,10 @@ public class ComputerUtilCombat {
}
damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities);
if (!attacker.hasKeyword("Infect")) {
if (!attacker.hasKeyword(Keyword.INFECT)) {
sum = ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true);
if (attacker.hasKeyword("Double Strike")) {
sum += ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true);
if (attacker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
sum *= 2;
}
}
return sum;
@@ -226,14 +227,15 @@ public class ComputerUtilCombat {
int damage = attacker.getNetCombatDamage();
int poison = 0;
damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, null, false);
if (attacker.hasKeyword("Infect")) {
poison += ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true);
if (attacker.hasKeyword("Double Strike")) {
poison += ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true);
if (attacker.hasKeyword(Keyword.INFECT)) {
int pd = ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true);
poison += pd;
if (attacker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
poison += pd;
}
}
if (attacker.hasKeyword("Poisonous") && (damage > 0)) {
poison += attacker.getKeywordMagnitude("Poisonous");
if (attacker.hasKeyword(Keyword.POISONOUS) && (damage > 0)) {
poison += attacker.getKeywordMagnitude(Keyword.POISONOUS);
}
return poison;
}
@@ -301,9 +303,9 @@ public class ComputerUtilCombat {
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage "
+ "as though it weren't blocked.")) {
unblocked.add(attacker);
} else if (attacker.hasKeyword("Trample")
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
&& (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, blockers))) {
if (!attacker.hasKeyword("Infect")) {
if (!attacker.hasKeyword(Keyword.INFECT)) {
damage += ComputerUtilCombat.getAttack(attacker) - ComputerUtilCombat.totalShieldDamage(attacker, blockers);
}
}
@@ -330,6 +332,11 @@ public class ComputerUtilCombat {
*/
public static int resultingPoison(final Player ai, final Combat combat) {
// ai can't get poision counters, so the value can't change
if (!ai.canReceiveCounters(CounterType.POISON)) {
return ai.getPoisonCounters();
}
int poison = 0;
final List<Card> attackers = combat.getAttackersOf(ai);
@@ -343,13 +350,13 @@ public class ComputerUtilCombat {
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage"
+ " as though it weren't blocked.")) {
unblocked.add(attacker);
} else if (attacker.hasKeyword("Trample")
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
&& (ComputerUtilCombat.getAttack(attacker) > ComputerUtilCombat.totalShieldDamage(attacker, blockers))) {
if (attacker.hasKeyword("Infect")) {
if (attacker.hasKeyword(Keyword.INFECT)) {
poison += ComputerUtilCombat.getAttack(attacker) - ComputerUtilCombat.totalShieldDamage(attacker, blockers);
}
if (attacker.hasKeyword("Poisonous")) {
poison += attacker.getKeywordMagnitude("Poisonous");
if (attacker.hasKeyword(Keyword.POISONOUS)) {
poison += attacker.getKeywordMagnitude(Keyword.POISONOUS);
}
}
}
@@ -576,7 +583,7 @@ public class ComputerUtilCombat {
int defenderDamage = predictDamageByBlockerWithoutDoubleStrike(attacker, defender);
if (defender.hasKeyword("Double Strike")) {
if (defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
defenderDamage += predictDamageTo(attacker, defenderDamage, defender, true);
}
@@ -590,25 +597,26 @@ public class ComputerUtilCombat {
* @return
*/
private static int predictDamageByBlockerWithoutDoubleStrike(final Card attacker, final Card defender) {
if (attacker.getName().equals("Sylvan Basilisk") && !defender.hasKeyword("Indestructible")) {
if (attacker.getName().equals("Sylvan Basilisk") && !defender.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return 0;
}
int flankingMagnitude = 0;
if (attacker.hasKeyword("Flanking") && !defender.hasKeyword("Flanking")) {
if (attacker.hasKeyword(Keyword.FLANKING) && !defender.hasKeyword(Keyword.FLANKING)) {
flankingMagnitude = attacker.getAmountOfKeyword("Flanking");
flankingMagnitude = attacker.getAmountOfKeyword(Keyword.FLANKING);
if (flankingMagnitude >= defender.getNetToughness()) {
return 0;
}
if ((flankingMagnitude >= (defender.getNetToughness() - defender.getDamage()))
&& !defender.hasKeyword("Indestructible")) {
&& !defender.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return 0;
}
} // flanking
if (attacker.hasKeyword("Indestructible") && !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) {
if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE)
&& !(defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT))) {
return 0;
}
@@ -667,21 +675,21 @@ public class ComputerUtilCombat {
}
int flankingMagnitude = 0;
if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
flankingMagnitude = attacker.getAmountOfKeyword("Flanking");
flankingMagnitude = attacker.getAmountOfKeyword(Keyword.FLANKING);
if (flankingMagnitude >= blocker.getNetToughness()) {
return 0;
}
if ((flankingMagnitude >= (blocker.getNetToughness() - blocker.getDamage()))
&& !blocker.hasKeyword("Indestructible")) {
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return 0;
}
} // flanking
final int defBushidoMagnitude = blocker.getKeywordMagnitude("Bushido");
final int defBushidoMagnitude = blocker.getKeywordMagnitude(Keyword.BUSHIDO);
final int defenderDefense = (blocker.getLethalDamage() - flankingMagnitude) + defBushidoMagnitude;
@@ -727,16 +735,16 @@ public class ComputerUtilCombat {
for (final Card defender : blockers) {
if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, true)
&& !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) {
&& !(defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT))) {
return true;
}
if (defender.hasKeyword("First Strike") || defender.hasKeyword("Double Strike")) {
if (defender.hasKeyword(Keyword.FIRST_STRIKE) || defender.hasKeyword(Keyword.DOUBLE_STRIKE)) {
firstStrikeBlockerDmg += defender.getNetCombatDamage();
}
}
// Consider first strike and double strike
if (attacker.hasKeyword("First Strike") || attacker.hasKeyword("Double Strike")) {
if (attacker.hasKeyword(Keyword.FIRST_STRIKE) || attacker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
return firstStrikeBlockerDmg >= ComputerUtilCombat.getDamageToKill(attacker);
}
@@ -920,9 +928,9 @@ public class ComputerUtilCombat {
// if the attacker has first strike and wither the blocker will deal
// less damage than expected
if (dealsFirstStrikeDamage(attacker, withoutAbilities, null)
&& (attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))
&& (attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT))
&& !dealsFirstStrikeDamage(blocker, withoutAbilities, null)
&& !blocker.hasKeyword("CARDNAME can't have counters put on it.")) {
&& !blocker.canReceiveCounters(CounterType.M1M1)) {
power -= attacker.getNetCombatDamage();
}
@@ -1069,8 +1077,8 @@ public class ComputerUtilCombat {
public static int predictToughnessBonusOfBlocker(final Card attacker, final Card blocker, boolean withoutAbilities) {
int toughness = 0;
if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
toughness -= attacker.getAmountOfKeyword("Flanking");
if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
toughness -= attacker.getAmountOfKeyword(Keyword.FLANKING);
}
if (blocker.getName().equals("Shape Stealer")) {
@@ -1239,9 +1247,7 @@ public class ComputerUtilCombat {
//check Exalted only for the first attacker
if (combat != null && combat.getAttackers().isEmpty()) {
for (Card card : attacker.getController().getCardsIn(ZoneType.Battlefield)) {
power += card.getAmountOfKeyword("Exalted");
}
power += attacker.getController().countExaltedBonus();
}
// Serene Master switches power with attacker
@@ -1263,9 +1269,9 @@ public class ComputerUtilCombat {
// less damage than expected
if (null != blocker) {
if (ComputerUtilCombat.dealsFirstStrikeDamage(blocker, withoutAbilities, combat)
&& (blocker.hasKeyword("Wither") || blocker.hasKeyword("Infect"))
&& (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());
@@ -1446,9 +1452,7 @@ public class ComputerUtilCombat {
//check Exalted only for the first attacker
if (combat != null && combat.getAttackers().isEmpty()) {
for (Card card : attacker.getController().getCardsIn(ZoneType.Battlefield)) {
toughness += card.getAmountOfKeyword("Exalted");
}
toughness += attacker.getController().countExaltedBonus();
}
if (blocker != null && attacker.getName().equals("Shape Stealer")) {
@@ -1494,7 +1498,7 @@ public class ComputerUtilCombat {
} else if (params.containsKey("Affected") && params.get("Affected").contains("untapped")) {
final String valid = TextUtil.fastReplace(params.get("Affected"), "untapped", "Creature");
if (!attacker.isValid(valid, card.getController(), card, null)
|| attacker.hasKeyword("Vigilance")) {
|| attacker.hasKeyword(Keyword.VIGILANCE)) {
continue;
}
// remove the bonus, because it will no longer be granted
@@ -1647,7 +1651,7 @@ public class ComputerUtilCombat {
if (blocker.isEquippedBy("Godsend")) {
return true;
}
if (attacker.hasKeyword("Indestructible") || ComputerUtil.canRegenerate(attacker.getController(), attacker)) {
if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(attacker.getController(), attacker)) {
return false;
}
@@ -1712,12 +1716,12 @@ public class ComputerUtilCombat {
*/
public static boolean attackerCantBeDestroyedInCombat(Player ai, final Card attacker) {
// attacker is either indestructible or may regenerate
if (attacker.hasKeyword("Indestructible") || (ComputerUtil.canRegenerate(ai, attacker))) {
if (attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, attacker))) {
return true;
}
// attacker will regenerate
if (attacker.getShieldCount() > 0 && !attacker.hasKeyword("CARDNAME can't be regenerated.")) {
if (attacker.getShieldCount() > 0 && attacker.canBeShielded()) {
return true;
}
@@ -1766,24 +1770,24 @@ public class ComputerUtilCombat {
}
int flankingMagnitude = 0;
if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
flankingMagnitude = attacker.getAmountOfKeyword("Flanking");
flankingMagnitude = attacker.getAmountOfKeyword(Keyword.FLANKING);
if (flankingMagnitude >= blocker.getNetToughness()) {
return false;
}
if ((flankingMagnitude >= (blocker.getNetToughness() - blocker.getDamage()))
&& !blocker.hasKeyword("Indestructible")) {
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return false;
}
} // flanking
if (((attacker.hasKeyword("Indestructible") || (ComputerUtil.canRegenerate(ai, attacker) && !withoutAbilities))
&& !(blocker.hasKeyword("Wither") || blocker.hasKeyword("Infect")))
|| (attacker.hasKeyword("Persist") && !attacker.canReceiveCounters(CounterType.M1M1) && (attacker
if (((attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, attacker) && !withoutAbilities))
&& !(blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)))
|| (attacker.hasKeyword(Keyword.PERSIST) && !attacker.canReceiveCounters(CounterType.M1M1) && (attacker
.getCounters(CounterType.M1M1) == 0))
|| (attacker.hasKeyword("Undying") && !attacker.canReceiveCounters(CounterType.P1P1) && (attacker
|| (attacker.hasKeyword(Keyword.UNDYING) && !attacker.canReceiveCounters(CounterType.P1P1) && (attacker
.getCounters(CounterType.P1P1) == 0))) {
return false;
}
@@ -1830,7 +1834,7 @@ public class ComputerUtilCombat {
final int attackerLife = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities, withoutAttackerStaticAbilities);
if (blocker.hasKeyword("Double Strike")) {
if (blocker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
if (defenderDamage > 0 && (hasKeyword(blocker, "Deathtouch", withoutAbilities, combat) || attacker.hasSVar("DestroyWhenDamaged"))) {
return true;
}
@@ -1840,7 +1844,8 @@ public class ComputerUtilCombat {
// Attacker may kill the blocker before he can deal normal
// (secondary) damage
if (dealsFirstStrikeDamage(attacker, withoutAbilities, combat) && !blocker.hasKeyword("Indestructible")) {
if (dealsFirstStrikeDamage(attacker, withoutAbilities, combat)
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
if (attackerDamage >= defenderLife) {
return false;
}
@@ -1856,7 +1861,7 @@ public class ComputerUtilCombat {
else { // no double strike for defender
// Attacker may kill the blocker before he can deal any damage
if (dealsFirstStrikeDamage(attacker, withoutAbilities, combat)
&& !blocker.hasKeyword("Indestructible")
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)
&& !dealsFirstStrikeDamage(blocker, withoutAbilities, combat)) {
if (attackerDamage >= defenderLife) {
@@ -1895,7 +1900,7 @@ public class ComputerUtilCombat {
for (Card attacker : attackers) {
if (ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, true)
&& !(attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))) {
&& !(attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT))) {
return true;
}
}
@@ -1913,19 +1918,21 @@ public class ComputerUtilCombat {
}
int flankingMagnitude = 0;
if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) {
if (attacker.hasKeyword(Keyword.FLANKING) && !blocker.hasKeyword(Keyword.FLANKING)) {
flankingMagnitude = attacker.getAmountOfKeyword("Flanking");
flankingMagnitude = attacker.getAmountOfKeyword(Keyword.FLANKING);
if (flankingMagnitude >= blocker.getNetToughness()) {
return true;
}
if ((flankingMagnitude >= ComputerUtilCombat.getDamageToKill(blocker)) && !blocker.hasKeyword("Indestructible")) {
if ((flankingMagnitude >= ComputerUtilCombat.getDamageToKill(blocker))
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return true;
}
} // flanking
if (blocker.hasKeyword("Indestructible") || dontTestRegen || ComputerUtil.canRegenerate(blocker.getController(), blocker)) {
if (blocker.hasKeyword(Keyword.INDESTRUCTIBLE) || dontTestRegen
|| ComputerUtil.canRegenerate(blocker.getController(), blocker)) {
return false;
}
@@ -2010,11 +2017,11 @@ public class ComputerUtilCombat {
return true;
}
if (((blocker.hasKeyword("Indestructible") || (ComputerUtil.canRegenerate(ai, blocker) && !withoutAbilities)) && !(attacker
.hasKeyword("Wither") || attacker.hasKeyword("Infect")))
|| (blocker.hasKeyword("Persist") && !blocker.canReceiveCounters(CounterType.M1M1) && (blocker
if (((blocker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, blocker) && !withoutAbilities)) && !(attacker
.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT)))
|| (blocker.hasKeyword(Keyword.PERSIST) && !blocker.canReceiveCounters(CounterType.M1M1) && (blocker
.getCounters(CounterType.M1M1) == 0))
|| (blocker.hasKeyword("Undying") && !blocker.canReceiveCounters(CounterType.P1P1) && (blocker
|| (blocker.hasKeyword(Keyword.UNDYING) && !blocker.canReceiveCounters(CounterType.P1P1) && (blocker
.getCounters(CounterType.P1P1) == 0))) {
return false;
}
@@ -2064,7 +2071,7 @@ public class ComputerUtilCombat {
final int attackerLife = ComputerUtilCombat.getDamageToKill(attacker)
+ ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, withoutAbilities, withoutAttackerStaticAbilities);
if (attacker.hasKeyword("Double Strike")) {
if (attacker.hasKeyword(Keyword.DOUBLE_STRIKE)) {
if (attackerDamage > 0 && (hasKeyword(attacker, "Deathtouch", withoutAbilities, combat) || blocker.hasSVar("DestroyWhenDamaged"))) {
return true;
}
@@ -2074,7 +2081,8 @@ public class ComputerUtilCombat {
// Attacker may kill the blocker before he can deal normal
// (secondary) damage
if (dealsFirstStrikeDamage(blocker, withoutAbilities, combat) && !attacker.hasKeyword("Indestructible")) {
if (dealsFirstStrikeDamage(blocker, withoutAbilities, combat)
&& !attacker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
if (defenderDamage >= attackerLife) {
return false;
}
@@ -2089,7 +2097,8 @@ public class ComputerUtilCombat {
else { // no double strike for attacker
// Defender may kill the attacker before he can deal any damage
if (dealsFirstStrikeDamage(blocker, withoutAbilities, combat) && !attacker.hasKeyword("Indestructible")
if (dealsFirstStrikeDamage(blocker, withoutAbilities, combat)
&& !attacker.hasKeyword(Keyword.INDESTRUCTIBLE)
&& !dealsFirstStrikeDamage(attacker, withoutAbilities, combat)) {
if (defenderDamage >= attackerLife) {
@@ -2136,7 +2145,7 @@ public class ComputerUtilCombat {
return damageMap;
}
final boolean hasTrample = attacker.hasKeyword("Trample");
final boolean hasTrample = attacker.hasKeyword(Keyword.TRAMPLE);
if (block.size() == 1) {
final Card blocker = block.getFirst();
@@ -2238,11 +2247,11 @@ public class ComputerUtilCombat {
final boolean noPrevention) {
final int killDamage = c.isPlaneswalker() ? c.getCurrentLoyalty() : ComputerUtilCombat.getDamageToKill(c);
if (c.hasKeyword("Indestructible") || c.getShieldCount() > 0) {
if (!(source.hasKeyword("Wither") || source.hasKeyword("Infect"))) {
if (c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getShieldCount() > 0) {
if (!(source.hasKeyword(Keyword.WITHER) || source.hasKeyword(Keyword.INFECT))) {
return maxDamage + 1;
}
} else if (source.hasKeyword("Deathtouch")) {
} else if (source.hasKeyword(Keyword.DEATHTOUCH)) {
for (int i = 1; i <= maxDamage; i++) {
if (noPrevention) {
if (c.staticReplaceDamage(i, source, isCombat) > 0) {
@@ -2405,7 +2414,7 @@ public class ComputerUtilCombat {
public final static boolean dealsFirstStrikeDamage(final Card combatant, final boolean withoutAbilities, final Combat combat) {
if (combatant.hasKeyword("Double Strike") || combatant.hasKeyword("First Strike")) {
if (combatant.hasKeyword(Keyword.DOUBLE_STRIKE) || combatant.hasKeyword(Keyword.FIRST_STRIKE)) {
return true;
}
@@ -2516,7 +2525,7 @@ public class ComputerUtilCombat {
public static boolean attackerHasThreateningAfflict(Card attacker, Player aiDefender) {
// TODO: expand this to account for more complex situations like the Wildfire Eternal unblocked trigger
int afflictDmg = attacker.getKeywordMagnitude("Afflict");
int afflictDmg = attacker.getKeywordMagnitude(Keyword.AFFLICT);
return afflictDmg > attacker.getNetPower() || afflictDmg >= aiDefender.getLife();
}
@@ -2541,18 +2550,10 @@ public class ComputerUtilCombat {
CardCollection withoutEvasion = new CardCollection();
for (Card atk : attackers) {
boolean hasProtection = false;
for (KeywordInterface inst : atk.getKeywords()) {
String kw = inst.getOriginal();
if (kw.startsWith("Protection")) {
hasProtection = true;
break;
}
}
if (atk.hasKeyword("Flying") || atk.hasKeyword("Shadow")
|| atk.hasKeyword("Horsemanship") || (atk.hasKeyword("Fear")
|| atk.hasKeyword("Intimidate") || atk.hasKeyword("Skulk") || hasProtection)) {
if (atk.hasKeyword(Keyword.FLYING) || atk.hasKeyword(Keyword.SHADOW)
|| atk.hasKeyword(Keyword.HORSEMANSHIP) || (atk.hasKeyword(Keyword.FEAR)
|| atk.hasKeyword(Keyword.INTIMIDATE) || atk.hasKeyword(Keyword.SKULK)
|| atk.hasKeyword(Keyword.PROTECTION))) {
withEvasion.add(atk);
} else {
withoutEvasion.add(atk);

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,13 +447,13 @@ public class ComputerUtilCost {
// Check for stuff like Nether Void
int extraManaNeeded = 0;
if (sa instanceof Spell) {
final boolean cannotBeCountered = sa.getHostCard().hasKeyword("CARDNAME can't be countered.");
final boolean cannotBeCountered = !CardFactoryUtil.isCounterable(sa.getHostCard());
for (Card c : player.getGame().getCardsIn(ZoneType.Battlefield)) {
final String snem = c.getSVar("AI_SpellsNeedExtraMana");
if (!StringUtils.isBlank(snem)) {
if (cannotBeCountered && c.getName().equals("Nether Void")) {
continue;
}
if (cannotBeCountered && c.getName().equals("Nether Void")) {
continue;
}
String[] parts = TextUtil.split(snem, ' ');
boolean meetsRestriction = parts.length == 1 || player.isValid(parts[1], c.getController(), c, sa);
if(!meetsRestriction)
@@ -467,7 +468,7 @@ public class ComputerUtilCost {
}
for (Card c : player.getCardsIn(ZoneType.Command)) {
if (cannotBeCountered) {
continue;
continue;
}
final String snem = c.getSVar("SpellsNeedExtraManaEffect");
if (!StringUtils.isBlank(snem)) {

View File

@@ -7,6 +7,7 @@ import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CounterType;
import forge.game.cost.CostPayEnergy;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.spellability.SpellAbility;
@@ -53,10 +54,10 @@ public class CreatureEvaluator implements Function<Card, Integer> {
}
// Evasion keywords
if (c.hasKeyword("Flying")) {
if (c.hasKeyword(Keyword.FLYING)) {
value += addValue(power * 10, "flying");
}
if (c.hasKeyword("Horsemanship")) {
if (c.hasKeyword(Keyword.HORSEMANSHIP)) {
value += addValue(power * 10, "horses");
}
if (c.hasKeyword("Unblockable")) {
@@ -65,13 +66,13 @@ public class CreatureEvaluator implements Function<Card, Integer> {
if (c.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
value += addValue(power * 6, "thorns");
}
if (c.hasKeyword("Fear")) {
if (c.hasKeyword(Keyword.FEAR)) {
value += addValue(power * 6, "fear");
}
if (c.hasKeyword("Intimidate")) {
if (c.hasKeyword(Keyword.INTIMIDATE)) {
value += addValue(power * 6, "intimidate");
}
if (c.hasStartOfKeyword("Menace")) {
if (c.hasKeyword(Keyword.MENACE)) {
value += addValue(power * 4, "menace");
}
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
@@ -81,49 +82,49 @@ public class CreatureEvaluator implements Function<Card, Integer> {
// Other good keywords
if (power > 0) {
if (c.hasKeyword("Double Strike")) {
if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
value += addValue(10 + (power * 15), "ds");
} else if (c.hasKeyword("First Strike")) {
} else if (c.hasKeyword(Keyword.FIRST_STRIKE)) {
value += addValue(10 + (power * 5), "fs");
}
if (c.hasKeyword("Deathtouch")) {
if (c.hasKeyword(Keyword.DEATHTOUCH)) {
value += addValue(25, "dt");
}
if (c.hasKeyword("Lifelink")) {
if (c.hasKeyword(Keyword.LIFELINK)) {
value += addValue(power * 10, "lifelink");
}
if (power > 1 && c.hasKeyword("Trample")) {
if (power > 1 && c.hasKeyword(Keyword.TRAMPLE)) {
value += addValue((power - 1) * 5, "trample");
}
if (c.hasKeyword("Vigilance")) {
if (c.hasKeyword(Keyword.VIGILANCE)) {
value += addValue((power * 5) + (toughness * 5), "vigilance");
}
if (c.hasKeyword("Wither")) {
if (c.hasKeyword(Keyword.WITHER)) {
value += addValue(power * 10, "Wither");
}
if (c.hasKeyword("Infect")) {
if (c.hasKeyword(Keyword.INFECT)) {
value += addValue(power * 15, "infect");
}
value += addValue(c.getKeywordMagnitude("Rampage"), "rampage");
value += addValue(c.getKeywordMagnitude("Afflict") * 5, "afflict");
value += addValue(c.getKeywordMagnitude(Keyword.RAMPAGE), "rampage");
value += addValue(c.getKeywordMagnitude(Keyword.AFFLICT) * 5, "afflict");
}
value += addValue(c.getKeywordMagnitude("Bushido") * 16, "bushido");
value += addValue(c.getAmountOfKeyword("Flanking") * 15, "flanking");
value += addValue(c.getAmountOfKeyword("Exalted") * 15, "exalted");
value += addValue(c.getKeywordMagnitude("Annihilator") * 50, "eldrazi");
value += addValue(c.getKeywordMagnitude("Absorb") * 11, "absorb");
value += addValue(c.getKeywordMagnitude(Keyword.BUSHIDO) * 16, "bushido");
value += addValue(c.getAmountOfKeyword(Keyword.FLANKING) * 15, "flanking");
value += addValue(c.getAmountOfKeyword(Keyword.EXALTED) * 15, "exalted");
value += addValue(c.getKeywordMagnitude(Keyword.ANNIHILATOR) * 50, "eldrazi");
value += addValue(c.getKeywordMagnitude(Keyword.ABSORB) * 11, "absorb");
// Keywords that may produce temporary or permanent buffs over time
if (c.hasKeyword("Prowess")) {
if (c.hasKeyword(Keyword.PROWESS)) {
value += addValue(5, "prowess");
}
if (c.hasKeyword("Outlast")) {
if (c.hasKeyword(Keyword.OUTLAST)) {
value += addValue(10, "outlast");
}
// Defensive Keywords
if (c.hasKeyword("Reach") && !c.hasKeyword("Flying")) {
if (c.hasKeyword(Keyword.REACH) && !c.hasKeyword(Keyword.FLYING)) {
value += addValue(5, "reach");
}
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
@@ -131,7 +132,7 @@ public class CreatureEvaluator implements Function<Card, Integer> {
}
// Protection
if (c.hasKeyword("Indestructible")) {
if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
value += addValue(70, "darksteel");
}
if (c.hasKeyword("Prevent all damage that would be dealt to CARDNAME.")) {
@@ -139,20 +140,17 @@ public class CreatureEvaluator implements Function<Card, Integer> {
} else if (c.hasKeyword("Prevent all combat damage that would be dealt to CARDNAME.")) {
value += addValue(50, "fogbank");
}
if (c.hasKeyword("Hexproof")) {
if (c.hasKeyword(Keyword.HEXPROOF)) {
value += addValue(35, "hexproof");
} else if (c.hasKeyword("Shroud")) {
} else if (c.hasKeyword(Keyword.SHROUD)) {
value += addValue(30, "shroud");
}
if (c.hasStartOfKeyword("Protection")) {
if (c.hasKeyword(Keyword.PROTECTION)) {
value += addValue(20, "protection");
}
if (c.hasStartOfKeyword("PreventAllDamageBy")) {
value += addValue(10, "prevent-dmg");
}
// Bad keywords
if (c.hasKeyword("Defender") || c.hasKeyword("CARDNAME can't attack.")) {
if (c.hasKeyword(Keyword.DEFENDER) || c.hasKeyword("CARDNAME can't attack.")) {
value -= subValue((power * 9) + 40, "defender");
} else if (c.getSVar("SacrificeEndCombat").equals("True")) {
value -= subValue(40, "sac-end");
@@ -188,17 +186,17 @@ public class CreatureEvaluator implements Function<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");
}
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
value -= subValue(20, "upkeep-dmg");
}
if (c.hasStartOfKeyword("Fading")) {
if (c.hasKeyword(Keyword.FADING)) {
value -= subValue(20, "fading");
}
if (c.hasStartOfKeyword("Vanishing")) {
if (c.hasKeyword(Keyword.VANISHING)) {
value -= subValue(20, "vanishing");
}
if (c.getSVar("Targeting").equals("Dies")) {

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

@@ -16,6 +16,7 @@ import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostRemoveCounter;
import forge.game.cost.CostSacrifice;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -157,7 +158,7 @@ public class CountersPutAi extends SpellAbilityAi {
// receive counters, execpt it has undying
CardCollection oppCreat = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterType.M1M1));
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, "Undying");
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
oppCreatM1 = CardLists.filter(oppCreatM1, new Predicate<Card>() {
@Override
@@ -359,14 +360,8 @@ public class CountersPutAi extends SpellAbilityAi {
if ("Polukranos".equals(logic)) {
CardCollection humCreatures = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
CardCollection targets = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
final CardCollection targets = CardLists.filter(humCreatures, new Predicate<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) {
@@ -811,12 +806,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()) {
@@ -833,7 +828,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()) {
@@ -926,7 +921,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;
}
@@ -939,7 +934,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;
}
@@ -964,7 +959,7 @@ public class CountersPutAi extends SpellAbilityAi {
if (e instanceof Card) {
Card c = (Card) e;
if (c.getController().isOpponentOf(ai)) {
if (options.contains(CounterType.M1M1) && !c.hasKeyword("Undying")) {
if (options.contains(CounterType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
return CounterType.M1M1;
}
for (CounterType type : options) {

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,12 +25,14 @@ public abstract class DamageAiBase extends SpellAbilityAi {
// Do not target a player if they aren't below 75% of our health.
// Unless Lifelink will cancel the damage to us
Card hostcard = sa.getHostCard();
boolean lifelink = hostcard.hasKeyword("Lifelink");
for (Card ench : hostcard.getEnchantedBy(false)) {
// Treat cards enchanted by older cards with "when enchanted creature deals damage, gain life" as if they had lifelink.
if (ench.hasSVar("LikeLifeLink")) {
if ("True".equals(ench.getSVar("LikeLifeLink"))) {
lifelink = true;
boolean lifelink = hostcard.hasKeyword(Keyword.LIFELINK);
if (!lifelink) {
for (Card ench : hostcard.getEnchantedBy(false)) {
// Treat cards enchanted by older cards with "when enchanted creature deals damage, gain life" as if they had lifelink.
if (ench.hasSVar("LikeLifeLink")) {
if ("True".equals(ench.getSVar("LikeLifeLink"))) {
lifelink = true;
}
}
}
}

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;
@@ -162,15 +163,15 @@ public class FightAi extends SpellAbilityAi {
// Get sorted creature lists
CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection humCreatures = ai.getOpponents().getCreaturesInPlay();
if ("Time to Feed".equals(sourceName)) { // flip sa
aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
} else {
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
}
if ("Time to Feed".equals(sourceName)) { // flip sa
aiCreatures = CardLists.getTargetableCards(aiCreatures, tgtFight);
aiCreatures = ComputerUtil.getSafeTargets(ai, tgtFight, aiCreatures);
humCreatures = CardLists.getTargetableCards(humCreatures, sa);
} else {
aiCreatures = CardLists.getTargetableCards(aiCreatures, sa);
aiCreatures = ComputerUtil.getSafeTargets(ai, sa, aiCreatures);
humCreatures = CardLists.getTargetableCards(humCreatures, tgtFight);
}
ComputerUtilCard.sortByEvaluateCreature(aiCreatures);
ComputerUtilCard.sortByEvaluateCreature(humCreatures);
if (humCreatures.isEmpty() || aiCreatures.isEmpty()) {
@@ -246,17 +247,19 @@ public class FightAi extends SpellAbilityAi {
}
return false;
}
public static boolean canKill(Card fighter, Card opponent, int pumpAttack) {
if (opponent.getSVar("Targeting").equals("Dies")) {
return true;
}
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed()
|| opponent.getShieldCount() > 0 || ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
return false;
}
if (fighter.hasKeyword("Deathtouch") || ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
return true;
}
return false;
if (opponent.getSVar("Targeting").equals("Dies")) {
return true;
}
if (opponent.hasProtectionFrom(fighter) || !opponent.canBeDestroyed() || opponent.getShieldCount() > 0
|| ComputerUtil.canRegenerate(opponent.getController(), opponent)) {
return false;
}
if (fighter.hasKeyword(Keyword.DEATHTOUCH)
|| ComputerUtilCombat.getDamageToKill(opponent) <= fighter.getNetPower() + pumpAttack) {
return true;
}
return false;
}
}

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,9 +304,9 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
} else if (keyword.equals("First Strike")) {
if (card.hasKeyword("Double Strike")) {
return false;
}
if (card.hasKeyword(Keyword.DOUBLE_STRIKE)) {
return false;
}
if (combat != null && combat.isBlocked(card) && !combat.getBlockers(card).isEmpty()) {
Card blocker = combat.getBlockers(card).get(0);
if (ComputerUtilCombat.canDestroyAttacker(ai, card, blocker, combat, true)
@@ -338,7 +344,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|| newPower <= 0
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)),
"Flanking").isEmpty()) {
Keyword.FLANKING).isEmpty()) {
return false;
}
} else if (keyword.startsWith("Trample")) {
@@ -353,7 +359,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
if (newPower <= 0) {
return false;
}
if (combat != null && combat.isBlocking(card) && !card.hasKeyword("Wither")) {
if (combat != null && combat.isBlocking(card) && !card.hasKeyword(Keyword.WITHER)) {
return true;
}
if ((ph.isPlayerTurn(opp))
@@ -362,7 +368,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
} else if (keyword.endsWith("Wither")) {
if (newPower <= 0 || card.hasKeyword("Infect")) {
if (newPower <= 0 || card.hasKeyword(Keyword.INFECT)) {
return false;
}
return combat != null && ( combat.isBlocking(card) || (combat.isAttacking(card) && combat.isBlocked(card)) );
@@ -375,14 +381,14 @@ public abstract class PumpAiBase extends SpellAbilityAi {
if (ph.isPlayerTurn(opp) || !CombatUtil.canAttack(card, opp)
|| newPower <= 0
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| CardLists.getNotKeyword(opp.getCreaturesInPlay(), "Defender").isEmpty()) {
|| CardLists.getNotKeyword(opp.getCreaturesInPlay(), Keyword.DEFENDER).isEmpty()) {
return false;
}
} else if (keyword.equals("Reach")) {
if (ph.isPlayerTurn(ai)
|| !ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| CardLists.getKeyword(game.getCombat().getAttackers(), "Flying").isEmpty()
|| card.hasKeyword("Flying")
|| CardLists.getKeyword(game.getCombat().getAttackers(), Keyword.FLYING).isEmpty()
|| card.hasKeyword(Keyword.FLYING)
|| !CombatUtil.canBlock(card)) {
return false;
}
@@ -409,7 +415,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
} else if (keyword.equals("Persist")) {
if (card.getBaseToughness() <= 1 || card.hasKeyword("Undying")) {
if (card.getBaseToughness() <= 1 || card.hasKeyword(Keyword.UNDYING)) {
return false;
}
} else if (keyword.equals("Islandwalk")) {
@@ -445,7 +451,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
} else if (keyword.endsWith("CARDNAME can attack as though it didn't have defender.")) {
if (!ph.isPlayerTurn(ai) || !card.hasKeyword("Defender")
if (!ph.isPlayerTurn(ai) || !card.hasKeyword(Keyword.DEFENDER)
|| ph.getPhase().isAfter(PhaseType.COMBAT_BEGIN)
|| card.isTapped() || newPower <= 0) {
return false;
@@ -510,7 +516,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
if (c.getSVar("Targeting").equals("Dies") || c.getNetToughness() <= -defense) {
return true; // can kill indestructible creatures
}
return (ComputerUtilCombat.getDamageToKill(c) <= -defense && !c.hasKeyword("Indestructible"));
return (ComputerUtilCombat.getDamageToKill(c) <= -defense && !c.hasKeyword(Keyword.INDESTRUCTIBLE));
}
}); // leaves all creatures that will be destroyed
} // -X/-X end

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;
@@ -200,29 +201,29 @@ public class TokenAi extends SpellAbilityAi {
if (sa.canTarget(ai)) {
sa.getTargets().add(ai);
} else {
//Flash Foliage
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
list = CardLists.getTargetableCards(list, sa);
CardCollection betterList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return c.getLethalDamage() == 1;
}
});
if (!betterList.isEmpty()) {
list = betterList;
}
betterList = CardLists.getNotKeyword(list, "Trample");
if (!betterList.isEmpty()) {
list = betterList;
}
if (!list.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
} else {
return false;
}
// Flash Foliage
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield),
ai.getOpponents());
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
list = CardLists.getTargetableCards(list, sa);
CardCollection betterList = CardLists.filter(list, new Predicate<Card>() {
@Override
public boolean apply(Card c) {
return c.getLethalDamage() == 1;
}
});
if (!betterList.isEmpty()) {
list = betterList;
}
betterList = CardLists.getNotKeyword(list, Keyword.TRAMPLE);
if (!betterList.isEmpty()) {
list = betterList;
}
if (!list.isEmpty()) {
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(list));
} else {
return false;
}
}
}
}

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> {
public abstract void visit(T object);
/**
* visit the object
* the Visitor should return true it can be visit again
* returning false means the outer function can stop
*
* @param object
* @return boolean
*/
public abstract boolean visit(T object);
public void visitAll(Iterable<? extends T> objects) {
public boolean visitAll(Iterable<? extends T> objects) {
for (T obj : objects) {
visit(obj);
if (!visit(obj)) {
return false;
}
}
return true;
}
}

View File

@@ -527,25 +527,57 @@ public class Game {
return cards;
}
public Card getCardState(final Card card) {
for (final Card c : getCardsInGame()) {
if (card.equals(c)) {
return c;
}
private static class CardStateVisitor extends Visitor<Card> {
Card found = null;
Card old = null;
private CardStateVisitor(final Card card) {
this.old = card;
}
return card;
@Override
public boolean visit(Card object) {
if (object.equals(old)) {
found = object;
}
return found == null;
}
public Card getFound() {
return found == null ? found : old;
}
}
public Card getCardState(final Card card) {
CardStateVisitor visit = new CardStateVisitor(card);
this.forEachCardInGame(visit);
return visit.getFound();
}
// Allows visiting cards in game without allocating a temporary list.
public void forEachCardInGame(Visitor<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());
}
@@ -553,8 +585,9 @@ public class Game {
final CardCollection all = new CardCollection();
Visitor<Card> visitor = new Visitor<Card>() {
@Override
public void visit(Card card) {
public boolean visit(Card card) {
all.add(card);
return true;
}
};
forEachCardInGame(visitor);

View File

@@ -792,7 +792,7 @@ public class GameAction {
game.forEachCardInGame(new Visitor<Card>() {
@Override
public void visit(final Card c) {
public boolean visit(final Card c) {
// need to get Card from preList if able
final Card co = preList.get(c);
for (int i = 0; i < co.getStaticAbilities().size(); i++) {
@@ -808,6 +808,7 @@ public class GameAction {
if (!co.getStaticCommandList().isEmpty()) {
staticList.add(co);
}
return true;
}
});

View File

@@ -31,6 +31,7 @@ import forge.game.ability.ApiType;
import forge.game.card.*;
import forge.game.card.CardPlayOption.PayManaCost;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.spellability.*;
@@ -482,8 +483,8 @@ public final class GameActionUtil {
}
}
if (source.hasKeyword("Conspire")) {
int amount = source.getAmountOfKeyword("Conspire");
if (source.hasKeyword(Keyword.CONSPIRE)) {
int amount = source.getAmountOfKeyword(Keyword.CONSPIRE);
for (int kwInstance = 1; kwInstance <= amount; kwInstance++) {
for (int i = 0; i < abilities.size(); i++) {
final SpellAbility newSA = abilities.get(i).copy();

View File

@@ -24,6 +24,7 @@ import forge.game.card.CardDamageMap;
import forge.game.card.CounterType;
import forge.game.event.GameEventCardAttachment;
import forge.game.event.GameEventCardAttachment.AttachMethod;
import forge.game.keyword.Keyword;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
import forge.util.collect.FCollection;
@@ -275,6 +276,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable {
}
public abstract boolean hasKeyword(final String keyword);
public abstract boolean hasKeyword(final Keyword keyword);
// GameEntities can now be Enchanted
public final CardCollectionView getEnchantedBy(boolean allowModify) {

View File

@@ -1007,11 +1007,11 @@ public class Card extends GameEntity implements Comparable<Card> {
}
public final boolean hasFirstStrike() {
return hasKeyword("First Strike");
return hasKeyword(Keyword.FIRST_STRIKE);
}
public final boolean hasDoubleStrike() {
return hasKeyword("Double Strike");
return hasKeyword(Keyword.DOUBLE_STRIKE);
}
public final boolean hasSecondStrike() {
@@ -2338,11 +2338,11 @@ public class Card extends GameEntity implements Comparable<Card> {
}
public final boolean hasSickness() {
return sickness && !hasKeyword("Haste");
return sickness && !hasKeyword(Keyword.HASTE);
}
public final boolean isSick() {
return sickness && isCreature() && !hasKeyword("Haste");
return sickness && isCreature() && !hasKeyword(Keyword.HASTE);
}
public boolean hasBecomeTargetThisTurn() {
@@ -3217,6 +3217,15 @@ public class Card extends GameEntity implements Comparable<Card> {
visitHiddenExtreinsicKeywords(visitor);
}
@Override
public final boolean hasKeyword(Keyword keyword) {
return hasKeyword(keyword, currentState);
}
public final boolean hasKeyword(Keyword key, CardState state) {
return state.hasKeyword(key);
}
@Override
public final boolean hasKeyword(String keyword) {
return hasKeyword(keyword, currentState);
@@ -3227,9 +3236,9 @@ public class Card extends GameEntity implements Comparable<Card> {
keyword = keyword.substring(7);
}
CountKeywordVisitor visitor = new CountKeywordVisitor(keyword);
HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, false);
visitKeywords(state, visitor);
return visitor.getCount() > 0;
return visitor.getResult();
}
public final void updateKeywords() {
@@ -3345,20 +3354,26 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
state.setCachedKeywords(keywords.getValues());
state.setCachedKeywords(keywords);
}
private void visitUnhiddenKeywords(CardState state, Visitor<KeywordInterface> visitor) {
if (changedCardKeywords.isEmpty()) {
// Fast path that doesn't involve temp allocations.
for (KeywordInterface kw : state.getIntrinsicKeywords()) {
visitor.visit(kw);
if (!visitor.visit(kw)) {
return;
}
}
for (KeywordInterface kw : extrinsicKeyword.getValues()) {
visitor.visit(kw);
if (!visitor.visit(kw)) {
return;
}
}
} else {
for (KeywordInterface kw : getUnhiddenKeywords(state)) {
visitor.visit(kw);
if (!visitor.visit(kw)) {
return;
}
}
}
}
@@ -3571,7 +3586,9 @@ public class Card extends GameEntity implements Comparable<Card> {
}
private void visitHiddenExtreinsicKeywords(Visitor<KeywordInterface> visitor) {
for (KeywordInterface inst : hiddenExtrinsicKeyword.getValues()) {
visitor.visit(inst);
if (!visitor.visit(inst)) {
return;
}
}
}
@@ -3820,18 +3837,18 @@ public class Card extends GameEntity implements Comparable<Card> {
return hasStartOfKeyword(keyword, currentState);
}
public final boolean hasStartOfKeyword(String keyword, CardState state) {
CountKeywordVisitor visitor = new CountKeywordVisitor(keyword, true);
HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true);
visitKeywords(state, visitor);
return visitor.getCount() > 0;
return visitor.getResult();
}
public final boolean hasStartOfUnHiddenKeyword(String keyword) {
return hasStartOfUnHiddenKeyword(keyword, currentState);
}
public final boolean hasStartOfUnHiddenKeyword(String keyword, CardState state) {
CountKeywordVisitor visitor = new CountKeywordVisitor(keyword, true);
HasKeywordVisitor visitor = new HasKeywordVisitor(keyword, true);
visitUnhiddenKeywords(state, visitor);
return visitor.getCount() > 0;
return visitor.getResult();
}
public final boolean hasAnyKeyword(final Iterable<String> keywords) {
@@ -3856,25 +3873,42 @@ public class Card extends GameEntity implements Comparable<Card> {
return visitor.getCount();
}
public final int getAmountOfKeyword(final Keyword k) {
return getAmountOfKeyword(k, currentState);
}
public final int getAmountOfKeyword(final Keyword k, CardState state) {
return state.getCachedKeyword(k).size();
}
// This is for keywords with a number like Bushido, Annihilator and Rampage.
// It returns the total.
public final int getKeywordMagnitude(final String k) {
public final int getKeywordMagnitude(final Keyword k) {
return getKeywordMagnitude(k, currentState);
}
public final int getKeywordMagnitude(final String k, CardState state) {
/**
* use it only for real keywords and not with hidden ones
*
* @param Keyword k
* @param CardState state
* @return Int
*/
public final int getKeywordMagnitude(final Keyword k, CardState state) {
int count = 0;
for (final KeywordInterface inst : getKeywords(state)) {
for (final KeywordInterface inst : state.getCachedKeyword(k)) {
String kw = inst.getOriginal();
if (kw.startsWith(k)) {
final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" ");
final String s = parse[1];
if (StringUtils.isNumeric(s)) {
count += Integer.parseInt(s);
} else {
String svar = StringUtils.join(parse);
if (state.hasSVar(svar)) {
count += AbilityUtils.calculateAmount(this, state.getSVar(svar), null);
}
// this can't be used yet for everything because of X values in Bushido X
// KeywordInterface#getAmount
// KeywordCollection#getAmount
final String[] parse = kw.contains(":") ? kw.split(":") : kw.split(" ");
final String s = parse[1];
if (StringUtils.isNumeric(s)) {
count += Integer.parseInt(s);
} else {
String svar = StringUtils.join(parse);
if (state.hasSVar(svar)) {
count += AbilityUtils.calculateAmount(this, state.getSVar(svar), null);
}
}
}
@@ -4538,7 +4572,7 @@ public class Card extends GameEntity implements Comparable<Card> {
final Game game = source.getGame();
boolean wither = (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.alwaysWither)
|| source.hasKeyword("Wither") || source.hasKeyword("Infect"));
|| source.hasKeyword(Keyword.WITHER) || source.hasKeyword(Keyword.INFECT));
if (isInPlay()) {
if (wither) {
@@ -4551,7 +4585,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
if (source.hasKeyword("Deathtouch") && isCreature()) {
if (source.hasKeyword(Keyword.DEATHTOUCH) && isCreature()) {
setHasBeenDealtDeathtouchDamage(true);
damageType = DamageType.Deathtouch;
}
@@ -4943,7 +4977,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
public final boolean canBeDestroyed() {
return isInPlay() && (!hasKeyword("Indestructible") || (isCreature() && getNetToughness() <= 0));
return isInPlay() && (!hasKeyword(Keyword.INDESTRUCTIBLE) || (isCreature() && getNetToughness() <= 0));
}
public final boolean canBeSacrificed() {
@@ -4983,10 +5017,7 @@ public class Card extends GameEntity implements Comparable<Card> {
final MutableBoolean result = new MutableBoolean(true);
visitKeywords(currentState, new Visitor<KeywordInterface>() {
@Override
public void visit(KeywordInterface kw) {
if (result.isFalse()) {
return;
}
public boolean visit(KeywordInterface kw) {
switch (kw.getOriginal()) {
case "Shroud":
StringBuilder sb = new StringBuilder();
@@ -5015,6 +5046,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
break;
}
return result.isTrue();
}
});
if (result.isFalse()) {
@@ -5506,11 +5538,12 @@ public class Card extends GameEntity implements Comparable<Card> {
}
@Override
public void visit(KeywordInterface inst) {
public boolean visit(KeywordInterface inst) {
final String kw = inst.getOriginal();
if ((startOf && kw.startsWith(keyword)) || kw.equals(keyword)) {
count++;
}
return true;
}
public int getCount() {
@@ -5518,13 +5551,38 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
private static final class HasKeywordVisitor extends Visitor<KeywordInterface> {
private String keyword;
private final MutableBoolean result = new MutableBoolean(false);
private boolean startOf;
private HasKeywordVisitor(String keyword, boolean startOf) {
this.keyword = keyword;
this.startOf = startOf;
}
@Override
public boolean visit(KeywordInterface inst) {
final String kw = inst.getOriginal();
if ((startOf && kw.startsWith(keyword)) || kw.equals(keyword)) {
result.setTrue();
}
return result.isFalse();
}
public boolean getResult() {
return result.isTrue();
}
}
// Collects all the keywords into a list.
private static final class ListKeywordVisitor extends Visitor<KeywordInterface> {
private List<KeywordInterface> keywords = Lists.newArrayList();
@Override
public void visit(KeywordInterface kw) {
public boolean visit(KeywordInterface kw) {
keywords.add(kw);
return true;
}
public List<KeywordInterface> getKeywords() {
@@ -5645,7 +5703,7 @@ public class Card extends GameEntity implements Comparable<Card> {
}
public boolean withFlash(Player p) {
if (hasKeyword("Flash")) {
if (hasKeyword(Keyword.FLASH)) {
return true;
}
if (withFlash.containsValue(p)) {

View File

@@ -12,6 +12,7 @@ import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import forge.game.GameEntity;
import forge.game.keyword.Keyword;
import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType;
@@ -53,7 +54,7 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
sourceLKI.getGame().getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false);
if (sourceLKI.hasKeyword("Lifelink")) {
if (sourceLKI.hasKeyword(Keyword.LIFELINK)) {
sourceLKI.getController().gainLife(sum, sourceLKI, sa);
}
}

View File

@@ -1196,7 +1196,7 @@ public class CardFactoryUtil {
}
if (sq[0].contains("BushidoPoint")) {
return doXMath(c.getKeywordMagnitude("Bushido"), m, c);
return doXMath(c.getKeywordMagnitude(Keyword.BUSHIDO), m, c);
}
if (sq[0].contains("TimesKicked")) {
return doXMath(c.getKickerMagnitude(), m, c);
@@ -4036,7 +4036,7 @@ public class CardFactoryUtil {
return false;
}
if (this.getHostCard().isInstant() || this.getHostCard().hasKeyword("Flash")) {
if (this.getHostCard().isInstant() || this.getHostCard().hasKeyword(Keyword.FLASH)) {
return true;
}

View File

@@ -26,6 +26,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.util.collect.FCollectionView;
@@ -235,7 +236,11 @@ public class CardLists {
return CardLists.filter(cardList, CardPredicates.isTargetableBy(source));
}
public static CardCollection getKeyword(Iterable<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));
}
@@ -243,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) {
@@ -250,6 +259,13 @@ public class CardLists {
}
return nKeyword;
}
public static int getAmountOfKeyword(final Iterable<Card> cardList, final Keyword keyword) {
int nKeyword = 0;
for (final Card c : cardList) {
nKeyword += c.getAmountOfKeyword(keyword);
}
return nKeyword;
}
// cardType is like "Land" or "Goblin", returns a new CardCollection that is a
// subset of current CardList
public static CardCollection getNotType(Iterable<Card> cardList, String cardType) {

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

@@ -19,7 +19,6 @@ package forge.game.card;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.card.*;
import forge.card.mana.ManaCost;
@@ -28,6 +27,7 @@ import forge.game.CardTraitBase;
import forge.game.ForgeScript;
import forge.game.GameObject;
import forge.game.card.CardView.CardStateView;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
@@ -60,7 +60,7 @@ public class CardState extends GameObject {
private String imageKey = "";
private Map<String, String> sVars = Maps.newTreeMap();
private List<KeywordInterface> cachedKeywords = Lists.newArrayList();
private KeywordCollection cachedKeywords = new KeywordCollection();
private CardRarity rarity = CardRarity.Unknown;
private String setCode = CardEdition.UNKNOWN.getCode();
@@ -167,12 +167,19 @@ public class CardState extends GameObject {
}
public final Collection<KeywordInterface> getCachedKeywords() {
return cachedKeywords;
return cachedKeywords.getValues();
}
public final void setCachedKeywords(final Collection<KeywordInterface> col) {
cachedKeywords.clear();
cachedKeywords.addAll(col);
public final Collection<KeywordInterface> getCachedKeyword(final Keyword keyword) {
return cachedKeywords.getValues(keyword);
}
public final void setCachedKeywords(final KeywordCollection col) {
cachedKeywords = col;
}
public final boolean hasKeyword(Keyword key) {
return cachedKeywords.contains(key);
}
public final Collection<KeywordInterface> getIntrinsicKeywords() {

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

@@ -27,6 +27,7 @@ import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardDamageMap;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.trigger.TriggerType;
@@ -692,7 +693,7 @@ public class Combat {
continue;
}
boolean trampler = attacker.hasKeyword("Trample");
boolean trampler = attacker.hasKeyword(Keyword.TRAMPLE);
orderedBlockers = blockersOrderedForDamageAssignment.get(attacker);
assignedDamage = true;
// If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender

View File

@@ -29,6 +29,7 @@ import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.card.*;
import forge.game.cost.Cost;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -658,7 +659,7 @@ public class CombatUtil {
return 0;
}
// TODO: remove CantBeBlockedByAmount LT2
if (attacker.hasKeyword("CantBeBlockedByAmount LT2") || attacker.hasKeyword("Menace")) {
if (attacker.hasKeyword("CantBeBlockedByAmount LT2") || attacker.hasKeyword(Keyword.MENACE)) {
return 2;
} else if (attacker.hasKeyword("CantBeBlockedByAmount LT3")) {
return 3;
@@ -702,7 +703,7 @@ public class CombatUtil {
for (final Card attacker : attackers) {
if (CombatUtil.canBlock(attacker, blocker, combat)) {
boolean must = true;
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("Menace")) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
final List<Card> possibleBlockers = Lists.newArrayList(defendersArmy);
possibleBlockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, possibleBlockers, combat)) {
@@ -806,7 +807,7 @@ public class CombatUtil {
for (final Card attacker : attackersWithLure) {
if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker)) {
boolean canBe = true;
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("Menace")) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
final List<Card> blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
blockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, blockers, combat)) {
@@ -824,7 +825,7 @@ public class CombatUtil {
if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker)
&& combat.isAttacking(attacker)) {
boolean canBe = true;
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("Menace")) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword(Keyword.MENACE)) {
final List<Card> blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
blockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, blockers, combat)) {
@@ -985,17 +986,17 @@ public class CombatUtil {
}
// rare case:
if (blocker.hasKeyword("Shadow")
if (blocker.hasKeyword(Keyword.SHADOW)
&& blocker.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
return false;
}
if (attacker.hasKeyword("Shadow") && !blocker.hasKeyword("Shadow")
if (attacker.hasKeyword(Keyword.SHADOW) && !blocker.hasKeyword(Keyword.SHADOW)
&& !blocker.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
return false;
}
if (!attacker.hasKeyword("Shadow") && blocker.hasKeyword("Shadow")) {
if (!attacker.hasKeyword(Keyword.SHADOW) && blocker.hasKeyword(Keyword.SHADOW)) {
return false;
}
@@ -1003,7 +1004,7 @@ public class CombatUtil {
return false;
}
if ((attacker.hasKeyword("Creatures with power greater than CARDNAME's power can't block it.") || attacker.hasKeyword("Skulk"))
if ((attacker.hasKeyword("Creatures with power greater than CARDNAME's power can't block it.") || attacker.hasKeyword(Keyword.SKULK))
&& attacker.getNetPower() < blocker.getNetPower()) {
return false;
}
@@ -1034,11 +1035,11 @@ public class CombatUtil {
}
}
if (blocker.hasKeyword("CARDNAME can block only creatures with flying.") && !attacker.hasKeyword("Flying")) {
if (blocker.hasKeyword("CARDNAME can block only creatures with flying.") && !attacker.hasKeyword(Keyword.FLYING)) {
return false;
}
if (attacker.hasKeyword("Flying") && !blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) {
if (attacker.hasKeyword(Keyword.FLYING) && !blocker.hasKeyword(Keyword.FLYING) && !blocker.hasKeyword(Keyword.REACH)) {
boolean stillblock = false;
for (KeywordInterface inst : blocker.getKeywords()) {
String k = inst.getOriginal();
@@ -1055,15 +1056,16 @@ public class CombatUtil {
}
}
if (attacker.hasKeyword("Horsemanship") && !blocker.hasKeyword("Horsemanship")) {
if (attacker.hasKeyword(Keyword.HORSEMANSHIP) && !blocker.hasKeyword(Keyword.HORSEMANSHIP)) {
return false;
}
if (attacker.hasKeyword("Fear") && !blocker.isArtifact() && !blocker.isBlack()) {
// color is hardcoded there
if (attacker.hasKeyword(Keyword.FEAR) && !blocker.isArtifact() && !blocker.isBlack()) {
return false;
}
if (attacker.hasKeyword("Intimidate") && !blocker.isArtifact() && !blocker.sharesColorWith(attacker)) {
if (attacker.hasKeyword(Keyword.INTIMIDATE) && !blocker.isArtifact() && !blocker.sharesColorWith(attacker)) {
return false;
}

View File

@@ -10,6 +10,7 @@ import forge.game.Game;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.*;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player;
@@ -210,7 +211,7 @@ public class CostAdjustment {
}
if (sa.isSpell()) {
if (sa.getHostCard().hasKeyword("Delve")) {
if (sa.getHostCard().hasKeyword(Keyword.DELVE)) {
sa.getHostCard().clearDelved();
final CardCollection delved = new CardCollection();
@@ -235,10 +236,10 @@ public class CostAdjustment {
game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false);
}
}
if (sa.getHostCard().hasKeyword("Convoke")) {
if (sa.getHostCard().hasKeyword(Keyword.CONVOKE)) {
adjustCostByConvokeOrImprovise(cost, sa, false, test);
}
if (sa.getHostCard().hasKeyword("Improvise")) {
if (sa.getHostCard().hasKeyword(Keyword.IMPROVISE)) {
adjustCostByConvokeOrImprovise(cost, sa, true, test);
}
} // isSpell

View File

@@ -143,6 +143,10 @@ public class KeywordCollection implements Iterable<String>, Serializable {
return map.values();
}
public Collection<KeywordInterface> getValues(final Keyword keyword) {
return map.get(keyword);
}
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {

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

@@ -30,6 +30,7 @@ import forge.game.ability.effects.DetachedCardEffect;
import forge.game.card.*;
import forge.game.card.CardPredicates.Presets;
import forge.game.event.*;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordCollection;
import forge.game.keyword.KeywordInterface;
import forge.game.keyword.KeywordCollection.KeywordCollectionView;
@@ -529,7 +530,7 @@ public class Player extends GameEntity implements Comparable<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) {
@@ -1070,6 +1071,11 @@ public class Player extends GameEntity implements Comparable<Player> {
return keywords.contains(keyword);
}
@Override
public final boolean hasKeyword(final Keyword keyword) {
return keywords.contains(keyword);
}
private void updateKeywords() {
keywords.clear();
@@ -2773,4 +2779,8 @@ public class Player extends GameEntity implements Comparable<Player> {
this.updateZoneForView(com);
}
public final int countExaltedBonus() {
return CardLists.getAmountOfKeyword(this.getCardsIn(ZoneType.Battlefield), Keyword.EXALTED);
}
}

View File

@@ -86,7 +86,7 @@ public class ReplacementHandler {
// Round up Static replacement effects
game.forEachCardInGame(new Visitor<Card>() {
@Override
public void visit(Card crd) {
public boolean visit(Card crd) {
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
// Use "CheckLKIZone" parameter to test for effects that care abut where the card was last (e.g. Kalitas, Traitor of Ghet
@@ -111,6 +111,7 @@ public class ReplacementHandler {
possibleReplacers.add(replacementEffect);
}
}
return true;
}
});
@@ -307,7 +308,7 @@ public class ReplacementHandler {
public void cleanUpTemporaryReplacements() {
game.forEachCardInGame(new Visitor<Card>() {
@Override
public void visit(Card c) {
public boolean visit(Card c) {
for (int i = 0; i < c.getReplacementEffects().size(); i++) {
ReplacementEffect rep = c.getReplacementEffects().get(i);
if (rep.isTemporary()) {
@@ -315,14 +316,16 @@ public class ReplacementHandler {
i--;
}
}
return true;
}
});
game.forEachCardInGame(new Visitor<Card>() {
@Override
public void visit(Card c) {
public boolean visit(Card c) {
for (int i = 0; i < c.getReplacementEffects().size(); i++) {
c.getReplacementEffects().get(i).setTemporarilySuppressed(false);
}
return true;
}
});
}

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

@@ -61,7 +61,7 @@ public class TriggerHandler {
public final void cleanUpTemporaryTriggers() {
game.forEachCardInGame(new Visitor<Card>() {
@Override
public void visit(Card c) {
public boolean visit(Card c) {
boolean changed = false;
for (int i = 0; i < c.getTriggers().size(); i++) {
Trigger trigger = c.getTriggers().get(i);
@@ -74,11 +74,12 @@ public class TriggerHandler {
if (changed) {
c.updateStateForView();
}
return true;
}
});
game.forEachCardInGame(new Visitor<Card>() {
@Override
public void visit(Card c) {
public boolean visit(Card c) {
boolean changed = false;
for (int i = 0; i < c.getTriggers().size(); i++) {
if (c.getTriggers().get(i).isSuppressed()) {
@@ -89,6 +90,7 @@ public class TriggerHandler {
if (changed) {
c.updateStateForView();
}
return true;
}
});
}
@@ -226,12 +228,13 @@ public class TriggerHandler {
activeTriggers.clear();
game.forEachCardInGame(new Visitor<Card>() {
@Override
public void visit(Card c) {
public boolean visit(Card c) {
for (final Trigger t : c.getTriggers()) {
if (isTriggerActive(t)) {
activeTriggers.add(t);
}
}
return true;
}
});
}

View File

@@ -51,6 +51,7 @@ import forge.game.event.GameEventSpellAbilityCast;
import forge.game.event.GameEventSpellRemovedFromStack;
import forge.game.event.GameEventSpellResolved;
import forge.game.event.GameEventZone;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerController.ManaPaymentPurpose;
import forge.game.replacement.ReplacementEffect;
@@ -119,7 +120,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
public final boolean isSplitSecondOnStack() {
for(SpellAbilityStackInstance si : stack) {
if (si.isSpell() && si.getSourceCard().hasKeyword("Split second")) {
if (si.isSpell() && si.getSourceCard().hasKeyword(Keyword.SPLIT_SECOND)) {
return true;
}
}

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(),