mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 01:38:13 +00:00
Simple check for AI if specific attacker should go for PW
This commit is contained in:
@@ -44,6 +44,7 @@ import forge.game.combat.CombatUtil;
|
||||
import forge.game.combat.GlobalAttackRestrictions;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
@@ -53,6 +54,7 @@ import forge.util.Aggregates;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
|
||||
@@ -207,7 +209,7 @@ public class AiAttackController {
|
||||
* a {@link forge.game.combat.Combat} object.
|
||||
* @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 ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) {
|
||||
return false;
|
||||
@@ -238,10 +240,8 @@ public class AiAttackController {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Player opp = this.defendingOpponent;
|
||||
|
||||
// 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) {
|
||||
boolean onlyIfExalted = false;
|
||||
if (combat.getAttackers().isEmpty() && ai.countExaltedBonus() > 0
|
||||
@@ -255,14 +255,14 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
// Poison opponent if unblocked
|
||||
if (ComputerUtilCombat.poisonIfUnblocked(attacker, opp) > 0) {
|
||||
if (defender instanceof Player && ComputerUtilCombat.poisonIfUnblocked(attacker, (Player) defender) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO check if that makes sense
|
||||
int exalted = ai.countExaltedBonus();
|
||||
if (this.attackers.size() == 1 && exalted > 0
|
||||
&& ComputerUtilCombat.predictDamageTo(opp, exalted, attacker, true) > 0) {
|
||||
&& ComputerUtilCombat.predictDamageTo(defender, exalted, attacker, true) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ public class AiAttackController {
|
||||
|
||||
if ((blockersNeeded == 0 || finestHour) && !this.oppList.isEmpty()) {
|
||||
// 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) {
|
||||
// For Finest Hour, one creature could attack and get the bonus TWICE
|
||||
humanBasePower = humanBasePower + humanExaltedBonus;
|
||||
@@ -736,7 +736,7 @@ public class AiAttackController {
|
||||
} else if (attacker.getSVar("MustAttack").equals("True")) {
|
||||
mustAttack = true;
|
||||
} else if (attacker.hasSVar("EndOfTurnLeavePlay")
|
||||
&& isEffectiveAttacker(ai, attacker, combat)) {
|
||||
&& isEffectiveAttacker(ai, attacker, combat, defender)) {
|
||||
mustAttack = true;
|
||||
} else if (seasonOfTheWitch) {
|
||||
// TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack
|
||||
@@ -776,7 +776,7 @@ public class AiAttackController {
|
||||
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
|
||||
return;
|
||||
|
||||
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat)) {
|
||||
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
}
|
||||
}
|
||||
@@ -817,7 +817,7 @@ public class AiAttackController {
|
||||
System.out.println("Exalted");
|
||||
this.aiAggression = 6;
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@@ -833,7 +833,7 @@ public class AiAttackController {
|
||||
// reached max, breakup
|
||||
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1059,72 +1059,59 @@ public class AiAttackController {
|
||||
if ( LOG_AI_ATTACKS )
|
||||
System.out.println("attackersLeft = " + attackersLeft);
|
||||
|
||||
for (int i = 0; i < attackersLeft.size(); i++) {
|
||||
final Card attacker = attackersLeft.get(i);
|
||||
if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike()
|
||||
&& ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, this.defendingOpponent)
|
||||
>= ComputerUtilCombat.getDamageToKill(attacker)) {
|
||||
continue;
|
||||
}
|
||||
FCollection<GameEntity> possibleDefenders = new FCollection<>(combat.getDefenders());
|
||||
|
||||
if (this.shouldAttack(ai, attacker, this.blockers, combat) && canAttackWrapper(attacker, defender)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
// check if attackers are enough to finish the attacked planeswalker
|
||||
if (defender instanceof Card) {
|
||||
Card pw = (Card) defender;
|
||||
final int blockNum = this.blockers.size();
|
||||
int attackNum = 0;
|
||||
int damage = 0;
|
||||
List<Card> attacking = combat.getAttackersOf(defender);
|
||||
CardLists.sortByPowerAsc(attacking);
|
||||
for (Card atta : attacking) {
|
||||
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers, combat)) {
|
||||
damage += ComputerUtilCombat.damageIfUnblocked(atta, opp, null, false);
|
||||
} else if (CombatUtil.canBeBlocked(attacker, this.blockers, combat)) {
|
||||
attackNum++;
|
||||
}
|
||||
}
|
||||
// if enough damage: switch to next planeswalker or player
|
||||
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
||||
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
|
||||
// look for next planeswalker
|
||||
for (Card walker : Lists.newArrayList(pwDefending)) {
|
||||
if (!combat.getAttackersOf(walker).isEmpty()) {
|
||||
pwDefending.remove(walker);
|
||||
while (true) {
|
||||
CardCollection attackersAssigned = new CardCollection();
|
||||
for (int i = 0; i < attackersLeft.size(); i++) {
|
||||
final Card attacker = attackersLeft.get(i);
|
||||
if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike()
|
||||
&& ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, this.defendingOpponent)
|
||||
>= ComputerUtilCombat.getDamageToKill(attacker)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shouldAttack(ai, attacker, this.blockers, combat, defender) && canAttackWrapper(attacker, defender)) {
|
||||
combat.addAttacker(attacker, defender);
|
||||
attackersAssigned.add(attacker);
|
||||
|
||||
// check if attackers are enough to finish the attacked planeswalker
|
||||
if (i < attackersLeft.size() - 1 && defender instanceof Card) {
|
||||
final int blockNum = this.blockers.size();
|
||||
int attackNum = 0;
|
||||
int damage = 0;
|
||||
List<Card> attacking = combat.getAttackersOf(defender);
|
||||
CardLists.sortByPowerDesc(attacking);
|
||||
for (Card atta : attacking) {
|
||||
if (attackNum >= blockNum || !CombatUtil.canBeBlocked(atta, this.blockers, combat)) {
|
||||
damage += ComputerUtilCombat.damageIfUnblocked(atta, defender, null, false);
|
||||
} else {
|
||||
attackNum++;
|
||||
}
|
||||
}
|
||||
if (pwDefending.isEmpty()) {
|
||||
defender = Collections.min(Lists.newArrayList(combat.getDefendingPlayers()), PlayerPredicates.compareByLife());
|
||||
}
|
||||
else {
|
||||
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(pwDefending);
|
||||
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
|
||||
// if enough damage: switch to next planeswalker or player
|
||||
if (damage >= ComputerUtilCombat.getDamageToKill((Card) defender)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attackersLeft.removeAll(attackersAssigned);
|
||||
possibleDefenders.remove(defender);
|
||||
if (attackersLeft.isEmpty() || possibleDefenders.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
CardCollection pwDefending = new CardCollection(Iterables.filter(possibleDefenders, Card.class));
|
||||
if (pwDefending.isEmpty()) {
|
||||
defender = Collections.min(new PlayerCollection(Iterables.filter(possibleDefenders, Player.class)), PlayerPredicates.compareByLife());
|
||||
} else {
|
||||
final Card pwNearUlti = ComputerUtilCard.getBestPlaneswalkerToDamage(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>
|
||||
* shouldAttack.
|
||||
@@ -1138,7 +1125,7 @@ public class AiAttackController {
|
||||
* a {@link forge.game.combat.Combat} object.
|
||||
* @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 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
|
||||
@@ -1169,7 +1156,7 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEffectiveAttacker(ai, attacker, combat)) {
|
||||
if (!isEffectiveAttacker(ai, attacker, combat, defender)) {
|
||||
return false;
|
||||
}
|
||||
boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator");
|
||||
@@ -1205,29 +1192,29 @@ public class AiAttackController {
|
||||
// look at the attacker in relation to the blockers to establish a
|
||||
// number of factors about the attacking context that will be relevant
|
||||
// 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 (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
||||
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)) {
|
||||
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
|
||||
if (isWorthLessThanAllKillers && !attacker.hasSVar("SacMe")
|
||||
&& ComputerUtilCard.evaluateCreature(defender) <= ComputerUtilCard.evaluateCreature(attacker)) {
|
||||
&& ComputerUtilCard.evaluateCreature(blocker) <= ComputerUtilCard.evaluateCreature(attacker)) {
|
||||
isWorthLessThanAllKillers = false;
|
||||
}
|
||||
}
|
||||
// see if this attacking creature can destroy this defender, if
|
||||
// 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;
|
||||
if (defender.getSVar("HasCombatEffect").equals("TRUE") || defender.getSVar("HasBlockEffect").equals("TRUE")) {
|
||||
if (blocker.getSVar("HasCombatEffect").equals("TRUE") || blocker.getSVar("HasBlockEffect").equals("TRUE")) {
|
||||
canKillAllDangerous = false;
|
||||
} else {
|
||||
if (defender.hasKeyword(Keyword.WITHER) || defender.hasKeyword(Keyword.INFECT)
|
||||
|| defender.hasKeyword(Keyword.LIFELINK)) {
|
||||
if (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)
|
||||
|| blocker.hasKeyword(Keyword.LIFELINK)) {
|
||||
canKillAllDangerous = false;
|
||||
// there is a creature that can survive an attack from this creature
|
||||
// and combat will have negative effects
|
||||
@@ -1371,7 +1358,6 @@ public class AiAttackController {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (missTarget) {
|
||||
|
||||
@@ -167,7 +167,6 @@ public class ComputerUtilCombat {
|
||||
return totalDamageOfBlockers(attacker, list);
|
||||
}
|
||||
|
||||
|
||||
// This function takes Doran and Double Strike into account
|
||||
/**
|
||||
* <p>
|
||||
@@ -202,10 +201,10 @@ public class ComputerUtilCombat {
|
||||
* a {@link forge.game.combat.Combat} object.
|
||||
* @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 sum = 0;
|
||||
if (!attacked.canLoseLife()) {
|
||||
if (attacked instanceof Player && !((Player) attacked).canLoseLife()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2139,7 +2138,6 @@ public class ComputerUtilCombat {
|
||||
return damageMap;
|
||||
} // setAssignedDamage()
|
||||
|
||||
|
||||
// how much damage is enough to kill the creature (for AI)
|
||||
/**
|
||||
* <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,
|
||||
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 (!(source.hasKeyword(Keyword.WITHER) || source.hasKeyword(Keyword.INFECT))) {
|
||||
return maxDamage + 1;
|
||||
}
|
||||
} else if (source.hasKeyword(Keyword.DEATHTOUCH)) {
|
||||
} else if (source.hasKeyword(Keyword.DEATHTOUCH) && !c.isPlaneswalker()) {
|
||||
for (int i = 1; i <= maxDamage; i++) {
|
||||
if (noPrevention) {
|
||||
if (c.staticReplaceDamage(i, source, isCombat) > 0) {
|
||||
@@ -2218,9 +2216,9 @@ public class ComputerUtilCombat {
|
||||
*/
|
||||
public final static int getDamageToKill(final Card c) {
|
||||
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")) {
|
||||
killDamage = 1 + damageShield;
|
||||
}
|
||||
|
||||
@@ -57,10 +57,10 @@ public class DamagePreventEffect extends DamagePreventEffectBase {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
Card host = sa.getHostCard();
|
||||
int numDam = AbilityUtils.calculateAmount(host, sa.getParam("Amount"), sa);
|
||||
|
||||
@@ -169,7 +169,6 @@ public class CardLists {
|
||||
Collections.sort(list, Collections.reverseOrder(PowerComparator));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Given a CardCollection c, return a CardCollection that contains a random amount of cards from c.
|
||||
|
||||
@@ -243,7 +243,6 @@ public class Combat {
|
||||
public final void addAttacker(final Card c, GameEntity defender) {
|
||||
addAttacker(c, defender, null);
|
||||
}
|
||||
|
||||
public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) {
|
||||
Collection<AttackingBand> attackersOfDefender = attackedByBands.get(defender);
|
||||
if (attackersOfDefender == null) {
|
||||
@@ -260,8 +259,7 @@ public class Combat {
|
||||
if (band == null || !attackersOfDefender.contains(band)) {
|
||||
band = new AttackingBand(c);
|
||||
attackersOfDefender.add(band);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
band.addAttacker(c);
|
||||
}
|
||||
c.updateAttackingForView();
|
||||
|
||||
Reference in New Issue
Block a user