Simple check for AI if specific attacker should go for PW

This commit is contained in:
TRT
2021-10-18 17:16:29 +02:00
committed by tool4EvEr
parent ff43491d07
commit ea65f77d16
5 changed files with 74 additions and 93 deletions

View File

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

View File

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

View File

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

View File

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

View File

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