Merge branch 'pwcombat' into 'master'

Smarter check for AI if specific attacker should go for PW

See merge request core-developers/forge!5585
This commit is contained in:
Michael Kamensky
2021-10-19 04:05:05 +00:00
17 changed files with 104 additions and 129 deletions

View File

@@ -18,7 +18,6 @@
package forge.ai; package forge.ai;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
@@ -44,7 +43,6 @@ import forge.game.combat.CombatUtil;
import forge.game.combat.GlobalAttackRestrictions; import forge.game.combat.GlobalAttackRestrictions;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
@@ -53,6 +51,7 @@ import forge.util.Aggregates;
import forge.util.Expressions; import forge.util.Expressions;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
@@ -207,7 +206,7 @@ public class AiAttackController {
* a {@link forge.game.combat.Combat} object. * a {@link forge.game.combat.Combat} object.
* @return a boolean. * @return a boolean.
*/ */
public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat) { public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat, final GameEntity defender) {
// if the attacker will die when attacking don't attack // if the attacker will die when attacking don't attack
if ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) { if ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) {
return false; return false;
@@ -238,10 +237,8 @@ public class AiAttackController {
return true; return true;
} }
final Player opp = this.defendingOpponent;
// Damage opponent if unblocked // Damage opponent if unblocked
final int dmgIfUnblocked = ComputerUtilCombat.damageIfUnblocked(attacker, opp, combat, true); final int dmgIfUnblocked = ComputerUtilCombat.damageIfUnblocked(attacker, defender, combat, true);
if (dmgIfUnblocked > 0) { if (dmgIfUnblocked > 0) {
boolean onlyIfExalted = false; boolean onlyIfExalted = false;
if (combat.getAttackers().isEmpty() && ai.countExaltedBonus() > 0 if (combat.getAttackers().isEmpty() && ai.countExaltedBonus() > 0
@@ -255,14 +252,14 @@ public class AiAttackController {
} }
} }
// Poison opponent if unblocked // Poison opponent if unblocked
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) { if (defender instanceof Player && ComputerUtilCombat.poisonIfUnblocked(attacker, (Player) defender) > 0) {
return true; return true;
} }
// TODO check if that makes sense // TODO check if that makes sense
int exalted = ai.countExaltedBonus(); int exalted = ai.countExaltedBonus();
if (this.attackers.size() == 1 && exalted > 0 if (this.attackers.size() == 1 && exalted > 0
&& ComputerUtilCombat.predictDamageTo(opp, exalted, attacker, true) > 0) { && ComputerUtilCombat.predictDamageTo(defender, exalted, attacker, true) > 0) {
return true; return true;
} }
@@ -315,7 +312,6 @@ public class AiAttackController {
} }
// this checks to make sure that the computer player doesn't lose when the human player attacks // this checks to make sure that the computer player doesn't lose when the human player attacks
// this method is used by getAttackers()
public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) { public final List<Card> notNeededAsBlockers(final Player ai, final List<Card> attackers) {
final List<Card> notNeededAsBlockers = new ArrayList<>(attackers); final List<Card> notNeededAsBlockers = new ArrayList<>(attackers);
int fixedBlockers = 0; int fixedBlockers = 0;
@@ -423,7 +419,7 @@ public class AiAttackController {
if ((blockersNeeded == 0 || finestHour) && !this.oppList.isEmpty()) { if ((blockersNeeded == 0 || finestHour) && !this.oppList.isEmpty()) {
// total attack = biggest creature + exalted, *2 if Rafiq is in play // total attack = biggest creature + exalted, *2 if Rafiq is in play
int humanBasePower = getAttack(this.oppList.get(0)) + humanExaltedBonus; int humanBasePower = ComputerUtilCombat.getAttack(this.oppList.get(0)) + humanExaltedBonus;
if (finestHour) { if (finestHour) {
// For Finest Hour, one creature could attack and get the bonus TWICE // For Finest Hour, one creature could attack and get the bonus TWICE
humanBasePower = humanBasePower + humanExaltedBonus; humanBasePower = humanBasePower + humanExaltedBonus;
@@ -649,9 +645,8 @@ public class AiAttackController {
if (-1 == n) { if (-1 == n) {
System.out.println("getMustAttackEntity() or getMustAttackEntityThisTurn() returned something not in defenders."); System.out.println("getMustAttackEntity() or getMustAttackEntityThisTurn() returned something not in defenders.");
return prefDefender; return prefDefender;
} else {
return entity;
} }
return entity;
} else { } else {
// 1. assault the opponent if you can kill him // 1. assault the opponent if you can kill him
if (bAssault) { if (bAssault) {
@@ -688,6 +683,7 @@ public class AiAttackController {
boolean tradeIfTappedOut = false; boolean tradeIfTappedOut = false;
int extraChanceIfOppHasMana = 0; int extraChanceIfOppHasMana = 0;
boolean tradeIfLowerLifePressure = false; boolean tradeIfLowerLifePressure = false;
boolean predictEvasion = false;
if (ai.getController().isAI()) { if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO); playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO);
@@ -695,6 +691,7 @@ public class AiAttackController {
tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT); tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA); extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA);
tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE); tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
predictEvasion = aic.getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION);
} }
final boolean bAssault = doAssault(ai); final boolean bAssault = doAssault(ai);
@@ -736,7 +733,7 @@ public class AiAttackController {
} else if (attacker.getSVar("MustAttack").equals("True")) { } else if (attacker.getSVar("MustAttack").equals("True")) {
mustAttack = true; mustAttack = true;
} else if (attacker.hasSVar("EndOfTurnLeavePlay") } else if (attacker.hasSVar("EndOfTurnLeavePlay")
&& isEffectiveAttacker(ai, attacker, combat)) { && isEffectiveAttacker(ai, attacker, combat, defender)) {
mustAttack = true; mustAttack = true;
} else if (seasonOfTheWitch) { } else if (seasonOfTheWitch) {
// TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack // TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack
@@ -767,7 +764,7 @@ public class AiAttackController {
return; return;
} }
if (bAssault) { if (bAssault && defender == this.defendingOpponent) { // in case we are forced to attack someone else
if (LOG_AI_ATTACKS) if (LOG_AI_ATTACKS)
System.out.println("Assault"); System.out.println("Assault");
CardLists.sortByPowerDesc(attackersLeft); CardLists.sortByPowerDesc(attackersLeft);
@@ -776,7 +773,7 @@ public class AiAttackController {
if (attackMax != -1 && combat.getAttackers().size() >= attackMax) if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
return; return;
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat)) { if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
} }
} }
@@ -817,7 +814,7 @@ public class AiAttackController {
System.out.println("Exalted"); System.out.println("Exalted");
this.aiAggression = 6; this.aiAggression = 6;
for (Card attacker : this.attackers) { for (Card attacker : this.attackers) {
if (canAttackWrapper(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) { if (canAttackWrapper(attacker, defender) && shouldAttack(ai, attacker, this.blockers, combat, defender)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
return; return;
} }
@@ -833,7 +830,7 @@ public class AiAttackController {
// reached max, breakup // reached max, breakup
if (attackMax != -1 && combat.getAttackers().size() >= attackMax) if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
break; break;
if (canAttackWrapper(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) { if (canAttackWrapper(attacker, defender) && shouldAttack(ai, attacker, this.blockers, combat, defender)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
} }
} }
@@ -853,7 +850,8 @@ public class AiAttackController {
final List<Card> nextTurnAttackers = new ArrayList<>(); final List<Card> nextTurnAttackers = new ArrayList<>();
int candidateCounterAttackDamage = 0; int candidateCounterAttackDamage = 0;
final Player opp = this.defendingOpponent; final Player opp = defender instanceof Player ? (Player) defender : ((Card)defender).getController();
this.oppList = getOpponentCreatures(opp);
// get the potential damage and strength of the AI forces // get the potential damage and strength of the AI forces
final List<Card> candidateAttackers = new ArrayList<>(); final List<Card> candidateAttackers = new ArrayList<>();
int candidateUnblockedDamage = 0; int candidateUnblockedDamage = 0;
@@ -867,9 +865,6 @@ public class AiAttackController {
} }
} }
boolean predictEvasion = (ai.getController().isAI()
&& ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION));
CardCollection categorizedOppList = new CardCollection(); CardCollection categorizedOppList = new CardCollection();
if (predictEvasion) { if (predictEvasion) {
// If predicting evasion, make sure that attackers with evasion are considered first // If predicting evasion, make sure that attackers with evasion are considered first
@@ -1059,6 +1054,11 @@ public class AiAttackController {
if ( LOG_AI_ATTACKS ) if ( LOG_AI_ATTACKS )
System.out.println("attackersLeft = " + attackersLeft); System.out.println("attackersLeft = " + attackersLeft);
FCollection<GameEntity> possibleDefenders = new FCollection<>(opp);
possibleDefenders.addAll(opp.getPlaneswalkersInPlay());
while (!attackersLeft.isEmpty()) {
CardCollection attackersAssigned = new CardCollection();
for (int i = 0; i < attackersLeft.size(); i++) { for (int i = 0; i < attackersLeft.size(); i++) {
final Card attacker = attackersLeft.get(i); final Card attacker = attackersLeft.get(i);
if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike() if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike()
@@ -1067,63 +1067,48 @@ public class AiAttackController {
continue; continue;
} }
if (this.shouldAttack(ai, attacker, this.blockers, combat) && canAttackWrapper(attacker, defender)) { if (shouldAttack(ai, attacker, this.blockers, combat, defender) && canAttackWrapper(attacker, defender)) {
combat.addAttacker(attacker, defender); combat.addAttacker(attacker, defender);
attackersAssigned.add(attacker);
// check if attackers are enough to finish the attacked planeswalker // check if attackers are enough to finish the attacked planeswalker
if (defender instanceof Card) { if (i < attackersLeft.size() - 1 && defender instanceof Card) {
Card pw = (Card) defender;
final int blockNum = this.blockers.size(); final int blockNum = this.blockers.size();
int attackNum = 0; int attackNum = 0;
int damage = 0; int damage = 0;
List<Card> attacking = combat.getAttackersOf(defender); List<Card> attacking = combat.getAttackersOf(defender);
CardLists.sortByPowerAsc(attacking); CardLists.sortByPowerDesc(attacking);
for (Card atta : attacking) { for (Card atta : attacking) {
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers, combat)) { if (attackNum >= blockNum || !CombatUtil.canBeBlocked(atta, this.blockers, combat)) {
damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null, false); damage += ComputerUtilCombat.damageIfUnblocked(atta, defender, null, false);
} else if (CombatUtil.canBeBlocked(attacker, this.blockers, combat)) { } else {
attackNum++; attackNum++;
} }
} }
// if enough damage: switch to next planeswalker or player // if enough damage: switch to next planeswalker
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) { if (damage >= ComputerUtilCombat.getDamageToKill((Card) defender)) {
List<Card> pwDefending = combat.getDefendingPlaneswalkers(); break;
// look for next planeswalker
for (Card walker : Lists.newArrayList(pwDefending)) {
if (!combat.getAttackersOf(walker).isEmpty()) {
pwDefending.remove(walker);
} }
} }
}
}
attackersLeft.removeAll(attackersAssigned);
possibleDefenders.remove(defender);
if (attackersLeft.isEmpty() || possibleDefenders.isEmpty()) {
break;
}
CardCollection pwDefending = new CardCollection(Iterables.filter(possibleDefenders, Card.class));
if (pwDefending.isEmpty()) { if (pwDefending.isEmpty()) {
defender = Collections.min(Lists.newArrayList(combat.getDefendingPlayers()), PlayerPredicates.compareByLife()); // TODO for now only looks at same player as we'd have to check the others from start too
} //defender = Collections.min(new PlayerCollection(Iterables.filter(possibleDefenders, Player.class)), PlayerPredicates.compareByLife());
else { defender = opp;
} else {
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending); final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending); defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
} }
} }
} }
}
}
} // getAttackers()
/**
* <p>
* getAttack.
* </p>
*
* @param c
* a {@link forge.game.card.Card} object.
* @return a int.
*/
public final static int getAttack(final Card c) {
int n = c.getNetCombatDamage();
if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
n *= 2;
}
return n;
}
/** /**
* <p> * <p>
@@ -1138,7 +1123,7 @@ public class AiAttackController {
* a {@link forge.game.combat.Combat} object. * a {@link forge.game.combat.Combat} object.
* @return a boolean. * @return a boolean.
*/ */
public final boolean shouldAttack(final Player ai, final Card attacker, final List<Card> defenders, final Combat combat) { public final boolean shouldAttack(final Player ai, final Card attacker, final List<Card> defenders, final Combat combat, final GameEntity defender) {
boolean canBeKilled = false; // indicates if the attacker can be killed boolean canBeKilled = false; // indicates if the attacker can be killed
boolean canBeKilledByOne = false; // indicates if the attacker can be killed by a single blocker boolean canBeKilledByOne = false; // indicates if the attacker can be killed by a single blocker
boolean canKillAll = true; // indicates if the attacker can kill all single blockers boolean canKillAll = true; // indicates if the attacker can kill all single blockers
@@ -1169,7 +1154,7 @@ public class AiAttackController {
} }
} }
if (!isEffectiveAttacker(ai, attacker, combat)) { if (!isEffectiveAttacker(ai, attacker, combat, defender)) {
return false; return false;
} }
boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator"); boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator");
@@ -1205,29 +1190,29 @@ public class AiAttackController {
// look at the attacker in relation to the blockers to establish a // look at the attacker in relation to the blockers to establish a
// number of factors about the attacking context that will be relevant // number of factors about the attacking context that will be relevant
// to the attackers decision according to the selected strategy // to the attackers decision according to the selected strategy
for (final Card defender : validBlockers) { for (final Card blocker : validBlockers) {
// if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check // if both isWorthLessThanAllKillers and canKillAllDangerous are false there's nothing more to check
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) { if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
numberOfPossibleBlockers += 1; numberOfPossibleBlockers += 1;
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false) if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.P1P1) == 0)) { && !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.P1P1) == 0)) {
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature 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 // see if the defending creature is of higher or lower
// value. We don't want to attack only to lose value // value. We don't want to attack only to lose value
if (isWorthLessThanAllKillers && !attacker.hasSVar("SacMe") if (isWorthLessThanAllKillers && !attacker.hasSVar("SacMe")
&& ComputerUtilCard.evaluateCreature(defender) <= ComputerUtilCard.evaluateCreature(attacker)) { && ComputerUtilCard.evaluateCreature(blocker) <= ComputerUtilCard.evaluateCreature(attacker)) {
isWorthLessThanAllKillers = false; isWorthLessThanAllKillers = false;
} }
} }
// see if this attacking creature can destroy this defender, if // see if this attacking creature can destroy this defender, if
// not record that it can't kill everything // not record that it can't kill everything
if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, defender, attacker, combat, false)) { if (canKillAllDangerous && !ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, false)) {
canKillAll = false; canKillAll = false;
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) { if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE")) {
canKillAllDangerous = false; canKillAllDangerous = false;
} else { } else {
if (defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT) if (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)
|| defender.hasKeyword(Keyword.LIFELINK)) { || blocker.hasKeyword(Keyword.LIFELINK)) {
canKillAllDangerous = false; canKillAllDangerous = false;
// there is a creature that can survive an attack from this creature // there is a creature that can survive an attack from this creature
// and combat will have negative effects // and combat will have negative effects
@@ -1371,7 +1356,6 @@ public class AiAttackController {
break; break;
} }
} }
} }
if (missTarget) { if (missTarget) {

View File

@@ -167,7 +167,6 @@ public class ComputerUtilCombat {
return totalDamageOfBlockers(attacker, list); return totalDamageOfBlockers(attacker, list);
} }
// This function takes Doran and Double Strike into account // This function takes Doran and Double Strike into account
/** /**
* <p> * <p>
@@ -202,10 +201,10 @@ public class ComputerUtilCombat {
* a {@link forge.game.combat.Combat} object. * a {@link forge.game.combat.Combat} object.
* @return a int. * @return a int.
*/ */
public static int damageIfUnblocked(final Card attacker, final Player attacked, final Combat combat, boolean withoutAbilities) { public static int damageIfUnblocked(final Card attacker, final GameEntity attacked, final Combat combat, boolean withoutAbilities) {
int damage = attacker.getNetCombatDamage(); int damage = attacker.getNetCombatDamage();
int sum = 0; int sum = 0;
if (!attacked.canLoseLife()) { if (attacked instanceof Player && !((Player) attacked).canLoseLife()) {
return 0; return 0;
} }
@@ -2139,7 +2138,6 @@ public class ComputerUtilCombat {
return damageMap; return damageMap;
} // setAssignedDamage() } // setAssignedDamage()
// how much damage is enough to kill the creature (for AI) // how much damage is enough to kill the creature (for AI)
/** /**
* <p> * <p>
@@ -2175,13 +2173,13 @@ public class ComputerUtilCombat {
*/ */
public static final int getEnoughDamageToKill(final Card c, final int maxDamage, final Card source, final boolean isCombat, public static final int getEnoughDamageToKill(final Card c, final int maxDamage, final Card source, final boolean isCombat,
final boolean noPrevention) { final boolean noPrevention) {
final int killDamage = c.isPlaneswalker() ? c.getCurrentLoyalty() : getDamageToKill(c); final int killDamage = getDamageToKill(c);
if (c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getShieldCount() > 0) { if (c.hasKeyword(Keyword.INDESTRUCTIBLE) || c.getShieldCount() > 0) {
if (!(source.hasKeyword(Keyword.WITHER) || source.hasKeyword(Keyword.INFECT))) { if (!(source.hasKeyword(Keyword.WITHER) || source.hasKeyword(Keyword.INFECT))) {
return maxDamage + 1; return maxDamage + 1;
} }
} else if (source.hasKeyword(Keyword.DEATHTOUCH)) { } else if (source.hasKeyword(Keyword.DEATHTOUCH) && !c.isPlaneswalker()) {
for (int i = 1; i <= maxDamage; i++) { for (int i = 1; i <= maxDamage; i++) {
if (noPrevention) { if (noPrevention) {
if (c.staticReplaceDamage(i, source, isCombat) > 0) { if (c.staticReplaceDamage(i, source, isCombat) > 0) {
@@ -2218,9 +2216,9 @@ public class ComputerUtilCombat {
*/ */
public final static int getDamageToKill(final Card c) { public final static int getDamageToKill(final Card c) {
int damageShield = c.getPreventNextDamageTotalShields(); int damageShield = c.getPreventNextDamageTotalShields();
int killDamage = c.getLethalDamage() + damageShield; int killDamage = (c.isPlaneswalker() ? c.getCurrentLoyalty() : c.getLethalDamage()) + damageShield;
if ((killDamage > damageShield) if (killDamage > damageShield
&& c.hasSVar("DestroyWhenDamaged")) { && c.hasSVar("DestroyWhenDamaged")) {
killDamage = 1 + damageShield; killDamage = 1 + damageShield;
} }

View File

@@ -152,8 +152,8 @@ public class PumpAi extends PumpAiBase {
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) final List<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & "))
: Lists.newArrayList(); : Lists.newArrayList();
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : ""; final String numDefense = sa.getParamOrDefault("NumDef", "");
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : ""; final String numAttack = sa.getParamOrDefault("NumAtt", "");
final String aiLogic = sa.getParamOrDefault("AILogic", ""); final String aiLogic = sa.getParamOrDefault("AILogic", "");
@@ -699,8 +699,8 @@ public class PumpAi extends PumpAiBase {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : ""; final String numDefense = sa.getParamOrDefault("NumDef", "");
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : ""; final String numAttack = sa.getParamOrDefault("NumAtt", "");
if (sa.getSVar("X").equals("Count$xPaid")) { if (sa.getSVar("X").equals("Count$xPaid")) {
sa.setXManaCostPaid(null); sa.setXManaCostPaid(null);
@@ -750,8 +750,8 @@ public class PumpAi extends PumpAiBase {
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final String numDefense = sa.hasParam("NumDef") ? sa.getParam("NumDef") : ""; final String numDefense = sa.getParamOrDefault("NumDef", "");
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : ""; final String numAttack = sa.getParamOrDefault("NumAtt", "");
if (numDefense.equals("-X") && sa.getSVar("X").equals("Count$ChosenNumber")) { if (numDefense.equals("-X") && sa.getSVar("X").equals("Count$ChosenNumber")) {
int energy = ai.getCounters(CounterEnumType.ENERGY); int energy = ai.getCounters(CounterEnumType.ENERGY);

View File

@@ -36,17 +36,17 @@ public class RepeatEachAi extends SpellAbilityAi {
return false; return false;
} else if ("CloneAllTokens".equals(logic)) { } else if ("CloneAllTokens".equals(logic)) {
List<Card> humTokenCreats = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), Presets.TOKEN); List<Card> humTokenCreats = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), Presets.TOKEN);
List<Card> compTokenCreats = CardLists.filter(aiPlayer.getCreaturesInPlay(), Presets.TOKEN); List<Card> compTokenCreats = aiPlayer.getTokensInPlay();
return compTokenCreats.size() > humTokenCreats.size(); return compTokenCreats.size() > humTokenCreats.size();
} else if ("BalanceLands".equals(logic)) { } else if ("BalanceLands".equals(logic)) {
if (CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield), Presets.LANDS).size() >= 5) { if (aiPlayer.getLandsInPlay().size() >= 5) {
return false; return false;
} }
List<Player> opponents = aiPlayer.getOpponents(); List<Player> opponents = aiPlayer.getOpponents();
for(Player opp : opponents) { for(Player opp : opponents) {
if (CardLists.filter(opp.getCardsIn(ZoneType.Battlefield), Presets.LANDS).size() < 4) { if (opp.getLandsInPlay().size() < 4) {
return false; return false;
} }
} }

View File

@@ -1644,7 +1644,7 @@ public class GameAction {
private boolean handlePlaneswalkerRule(Player p, CardZoneTable table) { private boolean handlePlaneswalkerRule(Player p, CardZoneTable table) {
// get all Planeswalkers // get all Planeswalkers
final List<Card> list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS); final List<Card> list = p.getPlaneswalkersInPlay();
boolean recheck = false; boolean recheck = false;
//final Multimap<String, Card> uniqueWalkers = ArrayListMultimap.create(); // Not used as of Ixalan //final Multimap<String, Card> uniqueWalkers = ArrayListMultimap.create(); // Not used as of Ixalan

View File

@@ -45,7 +45,6 @@ import forge.game.card.CardPredicates;
import forge.game.card.CardState; import forge.game.card.CardState;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CounterType; import forge.game.card.CounterType;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
@@ -2403,7 +2402,7 @@ public class AbilityUtils {
if (sq[0].startsWith("Domain")) { if (sq[0].startsWith("Domain")) {
int n = 0; int n = 0;
Player neededPlayer = sq[0].equals("DomainActivePlayer") ? game.getPhaseHandler().getPlayerTurn() : player; Player neededPlayer = sq[0].equals("DomainActivePlayer") ? game.getPhaseHandler().getPlayerTurn() : player;
CardCollection someCards = CardLists.filter(neededPlayer.getCardsIn(ZoneType.Battlefield), Presets.LANDS); CardCollection someCards = neededPlayer.getLandsInPlay();
for (String basic : MagicColor.Constant.BASIC_LANDS) { for (String basic : MagicColor.Constant.BASIC_LANDS) {
if (!CardLists.getType(someCards, basic).isEmpty()) { if (!CardLists.getType(someCards, basic).isEmpty()) {
n++; n++;

View File

@@ -24,7 +24,6 @@ import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
import forge.game.card.CounterType; import forge.game.card.CounterType;
@@ -132,7 +131,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
List<GameObject> tgtObjects = Lists.newArrayList(); List<GameObject> tgtObjects = Lists.newArrayList();
int divrem = 0; int divrem = 0;
if (sa.hasParam("Bolster")) { if (sa.hasParam("Bolster")) {
CardCollection creatsYouCtrl = CardLists.filter(activator.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); CardCollection creatsYouCtrl = activator.getCreaturesInPlay();
CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense)); CardCollection leastToughness = new CardCollection(Aggregates.listWithMin(creatsYouCtrl, CardPredicates.Accessors.fnGetDefense));
Map<String, Object> params = Maps.newHashMap(); Map<String, Object> params = Maps.newHashMap();

View File

@@ -169,7 +169,6 @@ public class CardLists {
Collections.sort(list, Collections.reverseOrder(PowerComparator)); Collections.sort(list, Collections.reverseOrder(PowerComparator));
} }
/** /**
* *
* Given a CardCollection c, return a CardCollection that contains a random amount of cards from c. * Given a CardCollection c, return a CardCollection that contains a random amount of cards from c.

View File

@@ -1479,7 +1479,7 @@ public class CardProperty {
if (StringUtils.isEmpty(what)) return combat.isBlocking(card); if (StringUtils.isEmpty(what)) return combat.isBlocking(card);
if (what.startsWith("Source")) return combat.isBlocking(card, source); if (what.startsWith("Source")) return combat.isBlocking(card, source);
if (what.startsWith("CreatureYouCtrl")) { if (what.startsWith("CreatureYouCtrl")) {
for (final Card c : CardLists.filter(sourceController.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) for (final Card c : sourceController.getCreaturesInPlay())
if (combat.isBlocking(card, c)) if (combat.isBlocking(card, c))
return true; return true;
return false; return false;

View File

@@ -13,8 +13,6 @@ import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -88,7 +86,7 @@ public class AttackRequirement {
if (c.hasKeyword("Each opponent must attack you or a planeswalker you control with at least one creature each combat if able.")) { if (c.hasKeyword("Each opponent must attack you or a planeswalker you control with at least one creature each combat if able.")) {
if (attacker.getController().isOpponentOf(c.getController()) && !defenderOrPWSpecific.containsKey(c.getController())) { if (attacker.getController().isOpponentOf(c.getController()) && !defenderOrPWSpecific.containsKey(c.getController())) {
defenderOrPWSpecific.put(c.getController(), 1); defenderOrPWSpecific.put(c.getController(), 1);
for (Card pw : CardLists.filter(c.getController().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { for (Card pw : c.getController().getPlaneswalkersInPlay()) {
// Add the attack alternatives that suffice (planeswalkers that can be attacked instead of the player) // Add the attack alternatives that suffice (planeswalkers that can be attacked instead of the player)
if (!defenderSpecificAlternatives.containsKey(c.getController())) { if (!defenderSpecificAlternatives.containsKey(c.getController())) {
defenderSpecificAlternatives.put(c.getController(), Lists.newArrayList()); defenderSpecificAlternatives.put(c.getController(), Lists.newArrayList());

View File

@@ -243,7 +243,6 @@ public class Combat {
public final void addAttacker(final Card c, GameEntity defender) { public final void addAttacker(final Card c, GameEntity defender) {
addAttacker(c, defender, null); addAttacker(c, defender, null);
} }
public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) { public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) {
Collection<AttackingBand> attackersOfDefender = attackedByBands.get(defender); Collection<AttackingBand> attackersOfDefender = attackedByBands.get(defender);
if (attackersOfDefender == null) { if (attackersOfDefender == null) {
@@ -260,8 +259,7 @@ public class Combat {
if (band == null || !attackersOfDefender.contains(band)) { if (band == null || !attackersOfDefender.contains(band)) {
band = new AttackingBand(c); band = new AttackingBand(c);
attackersOfDefender.add(band); attackersOfDefender.add(band);
} } else {
else {
band.addAttacker(c); band.addAttacker(c);
} }
c.updateAttackingForView(); c.updateAttackingForView();

View File

@@ -69,7 +69,7 @@ public class CombatUtil {
final FCollection<GameEntity> defenders = new FCollection<>(); final FCollection<GameEntity> defenders = new FCollection<>();
for (final Player defender : playerWhoAttacks.getOpponents()) { for (final Player defender : playerWhoAttacks.getOpponents()) {
defenders.add(defender); defenders.add(defender);
final CardCollection planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS); final CardCollection planeswalkers = defender.getPlaneswalkersInPlay();
defenders.addAll(planeswalkers); defenders.addAll(planeswalkers);
} }
return defenders; return defenders;

View File

@@ -2449,6 +2449,10 @@ public class Player extends GameEntity implements Comparable<Player> {
return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.CREATURES); return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
} }
public CardCollection getPlaneswalkersInPlay() {
return CardLists.filter(getCardsIn(ZoneType.Battlefield), Presets.PLANESWALKERS);
}
/** /**
* use to get a list of tokens in play for a given player. * use to get a list of tokens in play for a given player.
*/ */

View File

@@ -6,7 +6,7 @@ K:Flying
R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield. R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | DayTime$ Neither | ReplaceWith$ DoDay | Description$ If it's neither day nor night, it becomes day as CARDNAME enters the battlefield.
SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB SVar:DoDay:DB$ DayTime | Value$ Day | SubAbility$ ETB
SVar:ETB:DB$ InternalEtbReplacement SVar:ETB:DB$ InternalEtbReplacement
T:Mode$ DayTimeChanges | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ Whenever day becomes night or night becomes day, you may pay {1}{R}. If you do, return CARDNAME from your graveyard to the battlefield tapped. T:Mode$ DayTimeChanges | Execute$ TrigReturn | TriggerZones$ Graveyard | TriggerDescription$ Whenever day becomes night or night becomes day, you may pay {1}{R}. If you do, return CARDNAME from your graveyard to the battlefield tapped.
SVar:TrigReturn:AB$ ChangeZone | Cost$ 1 R | Origin$ Graveyard | Destination$ Battlefield | Tapped$ True SVar:TrigReturn:AB$ ChangeZone | Cost$ 1 R | Origin$ Graveyard | Destination$ Battlefield | Tapped$ True
DeckHas:Ability$Graveyard DeckHas:Ability$Graveyard
Oracle:Flying\nIf it's neither day nor night, it becomes day as Sunstreak Phoenix enters the battlefield.\nWhenever day becomes night or night becomes day, you may pay {1}{R}. If you do, return Sunstreak Phoenix from your graveyard to the battlefield tapped. Oracle:Flying\nIf it's neither day nor night, it becomes day as Sunstreak Phoenix enters the battlefield.\nWhenever day becomes night or night becomes day, you may pay {1}{R}. If you do, return Sunstreak Phoenix from your graveyard to the battlefield tapped.

View File

@@ -27,8 +27,6 @@ import forge.game.GameEntity;
import forge.game.GameEntityView; import forge.game.GameEntityView;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.combat.AttackingBand; import forge.game.combat.AttackingBand;
import forge.game.combat.Combat; import forge.game.combat.Combat;
@@ -128,7 +126,7 @@ public class InputAttack extends InputSyncronizedBase {
final List<Player> defenders = playerAttacks.getOpponents(); final List<Player> defenders = playerAttacks.getOpponents();
final Set<CardView> refreshCards = Sets.newHashSet(); final Set<CardView> refreshCards = Sets.newHashSet();
for (final Card c : CardLists.filter(playerAttacks.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) { for (final Card c : playerAttacks.getCreaturesInPlay()) {
if (combat.isAttacking(c)) { if (combat.isAttacking(c)) {
continue; continue;
} }

View File

@@ -20,8 +20,6 @@ package forge.gamemodes.match.input;
import java.util.List; import java.util.List;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.combat.CombatUtil; import forge.game.combat.CombatUtil;
@@ -60,7 +58,7 @@ public class InputBlock extends InputSyncronizedBase {
//auto-select first attacker to declare blockers for //auto-select first attacker to declare blockers for
for (final Card attacker : combat.getAttackers()) { for (final Card attacker : combat.getAttackers()) {
for (final Card c : CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) { for (final Card c : defender.getCreaturesInPlay()) {
if (CombatUtil.canBlock(attacker, c, combat)) { if (CombatUtil.canBlock(attacker, c, combat)) {
FThreads.invokeInEdtNowOrLater(new Runnable() { //must set current attacker on EDT FThreads.invokeInEdtNowOrLater(new Runnable() { //must set current attacker on EDT
@Override @Override