diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 7760c900b7d..385b4d68ebc 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -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 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 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 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 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() - /** - *

- * getAttack. - *

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

* 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 defenders, final Combat combat) { + public final boolean shouldAttack(final Player ai, final Card attacker, final List 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) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 3aac8b8d725..0cbd29fb9ed 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -167,7 +167,6 @@ public class ComputerUtilCombat { return totalDamageOfBlockers(attacker, list); } - // This function takes Doran and Double Strike into account /** *

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

@@ -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; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java index 173d1318758..b64ec1154ea 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamagePreventEffect.java @@ -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); diff --git a/forge-game/src/main/java/forge/game/card/CardLists.java b/forge-game/src/main/java/forge/game/card/CardLists.java index 093010da07d..7308ae667c1 100644 --- a/forge-game/src/main/java/forge/game/card/CardLists.java +++ b/forge-game/src/main/java/forge/game/card/CardLists.java @@ -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. diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index 9ab7719d0ef..88f41887e1c 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -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 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();