diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index 7c9cfc12a6c..602ac3886f3 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -7705,92 +7705,6 @@ public class Card extends GameEntity implements Comparable { this.dealtDamageToPlayerThisTurn.clear(); } - // how much damage is enough to kill the creature (for AI) - /** - *

- * getEnoughDamageToKill. - *

- * - * @param maxDamage - * a int. - * @param source - * a {@link forge.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - public final int getEnoughDamageToKill(final int maxDamage, final Card source, final boolean isCombat) { - return this.getEnoughDamageToKill(maxDamage, source, isCombat, false); - } - - /** - *

- * getEnoughDamageToKill. - *

- * - * @param maxDamage - * a int. - * @param source - * a {@link forge.Card} object. - * @param isCombat - * a boolean. - * @param noPrevention - * a boolean. - * @return a int. - */ - public final int getEnoughDamageToKill(final int maxDamage, final Card source, final boolean isCombat, - final boolean noPrevention) { - final int killDamage = this.getKillDamage(); - - if (this.hasKeyword("Indestructible") || (this.getShield() > 0)) { - if (!(source.hasKeyword("Wither") || source.hasKeyword("Infect"))) { - return maxDamage + 1; - } - } else if (source.hasKeyword("Deathtouch")) { - for (int i = 1; i <= maxDamage; i++) { - if (noPrevention) { - if (this.staticReplaceDamage(i, source, isCombat) > 0) { - return i; - } - } else if (this.predictDamage(i, source, isCombat) > 0) { - return i; - } - } - } - - for (int i = 1; i <= maxDamage; i++) { - if (noPrevention) { - if (this.staticReplaceDamage(i, source, isCombat) >= killDamage) { - return i; - } - } else { - if (this.predictDamage(i, source, isCombat) >= killDamage) { - return i; - } - } - } - - return maxDamage + 1; - } - - // the amount of damage needed to kill the creature (for AI) - /** - *

- * getKillDamage. - *

- * - * @return a int. - */ - public final int getKillDamage() { - int killDamage = this.getLethalDamage() + this.getPreventNextDamage(); - if ((killDamage > this.getPreventNextDamage()) - && this.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it.")) { - killDamage = 1 + this.getPreventNextDamage(); - } - - return killDamage; - } - // this is the minimal damage a trampling creature has to assign to a // blocker /** @@ -7935,34 +7849,6 @@ public class Card extends GameEntity implements Comparable { } } - // This function helps the AI calculate the actual amount of damage an - // effect would deal - /** - *

- * predictDamage. - *

- * - * @param damage - * a int. - * @param possiblePrevention - * a int. - * @param source - * a {@link forge.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - public final int predictDamage(final int damage, final int possiblePrevention, final Card source, - final boolean isCombat) { - - int restDamage = damage; - - restDamage = this.staticReplaceDamage(restDamage, source, isCombat); - restDamage = this.staticDamagePrevention(restDamage, possiblePrevention, source, isCombat); - - return restDamage; - } - // This should be also usable by the AI to forecast an effect (so it must // not change the game state) /** diff --git a/src/main/java/forge/GameEntity.java b/src/main/java/forge/GameEntity.java index ded2d27bd8d..0e54b6167d1 100644 --- a/src/main/java/forge/GameEntity.java +++ b/src/main/java/forge/GameEntity.java @@ -123,30 +123,6 @@ public abstract class GameEntity extends MyObservable { */ public abstract boolean addDamageAfterPrevention(final int damage, final Card source, final boolean isCombat); - /** - *

- * predictDamage. - *

- * - * @param damage - * a int. - * @param source - * a {@link forge.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - // This function helps the AI calculate the actual amount of damage an - // effect would deal - public int predictDamage(final int damage, final Card source, final boolean isCombat) { - - int restDamage = damage; - - restDamage = this.staticReplaceDamage(restDamage, source, isCombat); - restDamage = this.staticDamagePrevention(restDamage, source, isCombat); - - return restDamage; - } // This should be also usable by the AI to forecast an effect (so it must // not change the game state) diff --git a/src/main/java/forge/card/abilityfactory/ai/ChooseSourceAi.java b/src/main/java/forge/card/abilityfactory/ai/ChooseSourceAi.java index ef08ff0f720..e007f2f7df1 100644 --- a/src/main/java/forge/card/abilityfactory/ai/ChooseSourceAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/ChooseSourceAi.java @@ -93,7 +93,7 @@ public class ChooseSourceAi extends SpellAiLogic { return false; } int dmg = AbilityFactory.calculateAmount(threatSource, topStack.getParam("NumDmg"), topStack); - if (ai.predictDamage(dmg, threatSource, false) <= 0) { + if (ComputerUtilCombat.predictDamageTo(ai, dmg, threatSource, false) <= 0) { return false; } return true; diff --git a/src/main/java/forge/card/abilityfactory/ai/DamageAiBase.java b/src/main/java/forge/card/abilityfactory/ai/DamageAiBase.java index efb978feaef..1713647cc18 100644 --- a/src/main/java/forge/card/abilityfactory/ai/DamageAiBase.java +++ b/src/main/java/forge/card/abilityfactory/ai/DamageAiBase.java @@ -9,6 +9,7 @@ import forge.CardPredicates; import forge.Singletons; import forge.card.abilityfactory.SpellAiLogic; import forge.card.spellability.SpellAbility; +import forge.game.ai.ComputerUtilCombat; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -26,7 +27,7 @@ public abstract class DamageAiBase extends SpellAiLogic { } if (!noPrevention) { - restDamage = enemy.predictDamage(restDamage, sa.getSourceCard(), false); + restDamage = ComputerUtilCombat.predictDamageTo(enemy, restDamage, sa.getSourceCard(), false); } else { restDamage = enemy.staticReplaceDamage(restDamage, sa.getSourceCard(), false); } diff --git a/src/main/java/forge/card/abilityfactory/ai/DamageAllAi.java b/src/main/java/forge/card/abilityfactory/ai/DamageAllAi.java index 2042e63f0b0..0accacba0ff 100644 --- a/src/main/java/forge/card/abilityfactory/ai/DamageAllAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/DamageAllAi.java @@ -15,6 +15,7 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilMana; import forge.game.player.AIPlayer; @@ -69,7 +70,7 @@ public class DamageAllAi extends SpellAiLogic { // TODO: if damage is dependant on mana paid, maybe have X be human's max life // Don't kill yourself - if (validP.contains("Each") && (ai.getLife() <= ai.predictDamage(dmg, source, false))) { + if (validP.contains("Each") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) { return false; } @@ -80,7 +81,7 @@ public class DamageAllAi extends SpellAiLogic { // if we can kill human, do it if ((validP.contains("Each") || validP.contains("EachOpponent")) - && (opp.getLife() <= opp.predictDamage(dmg, source, false))) { + && (opp.getLife() <= ComputerUtilCombat.predictDamageTo(opp, dmg, source, false))) { return true; } @@ -133,13 +134,13 @@ public class DamageAllAi extends SpellAiLogic { computerList.clear(); } // Don't get yourself killed - if (validP.contains("Each") && (ai.getLife() <= ai.predictDamage(dmg, source, false))) { + if (validP.contains("Each") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) { return false; } // if we can kill human, do it if ((validP.contains("Each") || validP.contains("EachOpponent") || validP.contains("Targeted")) - && (enemy.getLife() <= enemy.predictDamage(dmg, source, false))) { + && (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) { return true; } @@ -177,7 +178,7 @@ public class DamageAllAi extends SpellAiLogic { final Predicate filterKillable = new Predicate() { @Override public boolean apply(final Card c) { - return (c.predictDamage(dmg, source, false) >= c.getKillDamage()); + return (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c)); } }; @@ -222,13 +223,13 @@ public class DamageAllAi extends SpellAiLogic { return true; } // Don't get yourself killed - if (validP.contains("Each") && (ai.getLife() <= ai.predictDamage(dmg, source, false))) { + if (validP.contains("Each") && (ai.getLife() <= ComputerUtilCombat.predictDamageTo(ai, dmg, source, false))) { return false; } // if we can kill human, do it if ((validP.contains("Each") || validP.contains("EachOpponent") || validP.contains("Targeted")) - && (enemy.getLife() <= enemy.predictDamage(dmg, source, false))) { + && (enemy.getLife() <= ComputerUtilCombat.predictDamageTo(enemy, dmg, source, false))) { return true; } diff --git a/src/main/java/forge/card/abilityfactory/ai/DamageDealAi.java b/src/main/java/forge/card/abilityfactory/ai/DamageDealAi.java index 73c72893692..51d769461a6 100644 --- a/src/main/java/forge/card/abilityfactory/ai/DamageDealAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/DamageDealAi.java @@ -16,6 +16,7 @@ import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; import forge.card.spellability.TargetSelection; import forge.game.ai.ComputerUtil; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilMana; import forge.game.phase.PhaseHandler; @@ -102,7 +103,7 @@ public class DamageDealAi extends DamageAiBase { final boolean noPrevention = sa.hasParam("NoPrevention"); final ArrayList cards = tgt.getTargetCards(); for (final Card c : cards) { - final int adjDamage = c.getEnoughDamageToKill(dmg, source, false, noPrevention); + final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); if ((adjDamage > actualPay) && (adjDamage <= dmg)) { actualPay = adjDamage; } @@ -156,7 +157,7 @@ public class DamageDealAi extends DamageAiBase { final List killables = CardLists.filter(hPlay, new Predicate() { @Override public boolean apply(final Card c) { - return (c.getEnoughDamageToKill(d, source, false, noPrevention) <= d) && !ComputerUtil.canRegenerate(ai, c) + return (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) && !ComputerUtil.canRegenerate(ai, c) && !(c.getSVar("SacMe").length() > 0); } }); @@ -322,7 +323,7 @@ public class DamageDealAi extends DamageAiBase { // Card c = (Card)o; } else if (o instanceof Player) { final Player p = (Player) o; - final int restDamage = p.predictDamage(dmg, saMe.getSourceCard(), false); + final int restDamage = ComputerUtilCombat.predictDamageTo(p, dmg, saMe.getSourceCard(), false); if (p == ai && p.canLoseLife() && ((restDamage + 3) >= p.getLife()) && (restDamage > 0)) { // from this spell will kill me return false; @@ -413,7 +414,7 @@ public class DamageDealAi extends DamageAiBase { actualPay = dmg; } for (final Card c : cards) { - final int adjDamage = c.getEnoughDamageToKill(dmg, source, false, noPrevention); + final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); if (adjDamage > actualPay) { actualPay = adjDamage; } diff --git a/src/main/java/forge/card/abilityfactory/ai/FightAi.java b/src/main/java/forge/card/abilityfactory/ai/FightAi.java index 51d569ced15..3bba9d13f0d 100644 --- a/src/main/java/forge/card/abilityfactory/ai/FightAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/FightAi.java @@ -10,6 +10,7 @@ import forge.CardLists; import forge.card.abilityfactory.SpellAiLogic; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCombat; import forge.game.player.AIPlayer; import forge.util.MyRandom; @@ -44,8 +45,8 @@ public class FightAi extends SpellAiLogic { if (humCreatures.size() > 0 && aiCreatures.size() > 0) { for (Card humanCreature : humCreatures) { for (Card aiCreature : aiCreatures) { - if (humanCreature.getKillDamage() <= aiCreature.getNetAttack() - && humanCreature.getNetAttack() < aiCreature.getKillDamage()) { + if (ComputerUtilCombat.getDamageToKill(humanCreature) <= aiCreature.getNetAttack() + && humanCreature.getNetAttack() < ComputerUtilCombat.getDamageToKill(aiCreature)) { // todo: check min/max targets; see if we picked the best matchup tgt.addTarget(humanCreature); tgt.addTarget(aiCreature); @@ -69,8 +70,8 @@ public class FightAi extends SpellAiLogic { && creature1.sharesCreatureTypeWith(creature2)) { continue; } - if (creature1.getKillDamage() <= creature2.getNetAttack() - && creature1.getNetAttack() >= creature2.getKillDamage()) { + if (ComputerUtilCombat.getDamageToKill(creature1) <= creature2.getNetAttack() + && creature1.getNetAttack() >= ComputerUtilCombat.getDamageToKill(creature2)) { // todo: check min/max targets; see if we picked the best matchup tgt.addTarget(creature1); tgt.addTarget(creature2); diff --git a/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java b/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java index 041b14ac7cc..4287abbc0ec 100644 --- a/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java +++ b/src/main/java/forge/card/abilityfactory/ai/PumpAiBase.java @@ -502,7 +502,7 @@ public abstract class PumpAiBase extends SpellAiLogic { if (c.getNetDefense() <= -defense) { return true; // can kill indestructible creatures } - return ((c.getKillDamage() <= -defense) && !c.hasKeyword("Indestructible")); + return ((ComputerUtilCombat.getDamageToKill(c) <= -defense) && !c.hasKeyword("Indestructible")); } }); // leaves all creatures that will be destroyed } // -X/-X end diff --git a/src/main/java/forge/card/abilityfactory/ai/PumpAllAi.java b/src/main/java/forge/card/abilityfactory/ai/PumpAllAi.java index c83e8fd7e35..bb7df0fefb0 100644 --- a/src/main/java/forge/card/abilityfactory/ai/PumpAllAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/PumpAllAi.java @@ -64,7 +64,7 @@ public class PumpAllAi extends PumpAiBase { if (c.getNetDefense() <= -defense) { return true; // can kill indestructible creatures } - return ((c.getKillDamage() <= -defense) && !c.hasKeyword("Indestructible")); + return ((ComputerUtilCombat.getDamageToKill(c) <= -defense) && !c.hasKeyword("Indestructible")); } }); // leaves all creatures that will be destroyed human = CardLists.filter(human, new Predicate() { @@ -73,7 +73,7 @@ public class PumpAllAi extends PumpAiBase { if (c.getNetDefense() <= -defense) { return true; // can kill indestructible creatures } - return ((c.getKillDamage() <= -defense) && !c.hasKeyword("Indestructible")); + return ((ComputerUtilCombat.getDamageToKill(c) <= -defense) && !c.hasKeyword("Indestructible")); } }); // leaves all creatures that will be destroyed } // -X/-X end diff --git a/src/main/java/forge/card/abilityfactory/effects/ChooseSourceEffect.java b/src/main/java/forge/card/abilityfactory/effects/ChooseSourceEffect.java index d1eb98e0b6f..316a0b72875 100644 --- a/src/main/java/forge/card/abilityfactory/effects/ChooseSourceEffect.java +++ b/src/main/java/forge/card/abilityfactory/effects/ChooseSourceEffect.java @@ -167,7 +167,7 @@ public class ChooseSourceEffect extends SpellEffect { break; } int dmg = AbilityFactory.calculateAmount(source, topStack.getParam("NumDmg"), topStack); - if (ai.predictDamage(dmg, source, false) <= 0) { + if (ComputerUtilCombat.predictDamageTo(ai, dmg, source, false) <= 0) { break; } chosen.add(topStack.getSourceCard()); diff --git a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java index 120028ecdcd..5e4fe5ce866 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java @@ -50,6 +50,7 @@ import forge.card.trigger.Trigger; import forge.card.trigger.TriggerHandler; import forge.control.input.Input; import forge.control.input.InputSelectManyCards; +import forge.game.ai.ComputerUtilCombat; import forge.game.player.Player; import forge.game.zone.PlayerZone; import forge.game.zone.Zone; @@ -232,7 +233,7 @@ public class CardFactoryCreatures { final List damageableWolves = CardLists.filter(wolves, new Predicate() { @Override public boolean apply(final Card c) { - return (c.predictDamage(target.getNetAttack(), target, false) > 0); + return (ComputerUtilCombat.predictDamageTo(c, target.getNetAttack(), target, false) > 0); } }); @@ -253,8 +254,8 @@ public class CardFactoryCreatures { wolvesLeft = CardLists.filter(wolvesLeft, new Predicate() { @Override public boolean apply(final Card c) { - return (c.getKillDamage() > 0) - && ((c.getKillDamage() <= target.getNetAttack()) || target + return (ComputerUtilCombat.getDamageToKill(c) > 0) + && ((ComputerUtilCombat.getDamageToKill(c) <= target.getNetAttack()) || target .hasKeyword("Deathtouch")); } }); @@ -263,7 +264,7 @@ public class CardFactoryCreatures { if (wolvesLeft.size() > 0) { final Card best = CardFactoryUtil.getBestCreatureAI(wolvesLeft); best.addDamage(1, target); - if ((best.getKillDamage() <= 0) || target.hasKeyword("Deathtouch")) { + if ((ComputerUtilCombat.getDamageToKill(best) <= 0) || target.hasKeyword("Deathtouch")) { wolvesLeft.remove(best); } } else { diff --git a/src/main/java/forge/game/ai/AiAttackController.java b/src/main/java/forge/game/ai/AiAttackController.java index 9c07fdc7826..afb6dadc113 100644 --- a/src/main/java/forge/game/ai/AiAttackController.java +++ b/src/main/java/forge/game/ai/AiAttackController.java @@ -786,7 +786,7 @@ public class AiAttackController { final Card attacker = attackersLeft.get(i); if (this.aiAggression < 5 && !attacker.hasFirstStrike() && !attacker.hasDoubleStrike() && ComputerUtilCombat.getTotalFirstStrikeBlockPower(attacker, ai.getOpponent()) - >= attacker.getKillDamage()) { + >= ComputerUtilCombat.getDamageToKill(attacker)) { continue; } diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index ad81118333c..1793dc2296e 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -1150,21 +1150,21 @@ public class ComputerUtil { // don't bounce or blink a permanent that the human // player owns or is a token - if (saviourApi == ApiType.ChangeZone && (c.getOwner().isHuman() || c.isToken())) { + if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) { continue; } - if (c.predictDamage(dmg, source, false) >= c.getKillDamage()) { + if (ComputerUtilCombat.predictDamageTo(c, dmg, source, false) >= ComputerUtilCombat.getDamageToKill(c)) { threatened.add(c); } } else if (o instanceof Player) { final Player p = (Player) o; if (source.hasKeyword("Infect")) { - if (p.predictDamage(dmg, source, false) >= p.getPoisonCounters()) { + if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getPoisonCounters()) { threatened.add(p); } - } else if (p.predictDamage(dmg, source, false) >= p.getLife()) { + } else if (ComputerUtilCombat.predictDamageTo(p, dmg, source, false) >= p.getLife()) { threatened.add(p); } } @@ -1224,7 +1224,7 @@ public class ComputerUtil { // don't bounce or blink a permanent that the human // player owns or is a token - if (saviourApi == ApiType.ChangeZone && (c.getOwner().isHuman() || c.isToken())) { + if (saviourApi == ApiType.ChangeZone && (c.getOwner().isOpponentOf(aiPlayer) || c.isToken())) { continue; } diff --git a/src/main/java/forge/game/ai/ComputerUtilBlock.java b/src/main/java/forge/game/ai/ComputerUtilBlock.java index b7ba67f67a6..576fd517e9b 100644 --- a/src/main/java/forge/game/ai/ComputerUtilBlock.java +++ b/src/main/java/forge/game/ai/ComputerUtilBlock.java @@ -426,7 +426,7 @@ public class ComputerUtilBlock { if (firstStrikeBlockers.size() > 1) { CardLists.sortAttack(firstStrikeBlockers); for (final Card blocker : firstStrikeBlockers) { - final int damageNeeded = attacker.getKillDamage() + final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); // if the total damage of the blockgang was not enough // without but is enough with this blocker finish the @@ -482,7 +482,7 @@ public class ComputerUtilBlock { final Card leader = CardFactoryUtil.getBestCreatureAI(usableBlockers); blockGang.add(leader); usableBlockers.remove(leader); - absorbedDamage = leader.getEnoughDamageToKill(attacker.getNetCombatDamage(), attacker, true); + absorbedDamage = ComputerUtilCombat.getEnoughDamageToKill(leader, attacker.getNetCombatDamage(), attacker, true); currentValue = CardFactoryUtil.evaluateCreature(leader); for (final Card blocker : usableBlockers) { @@ -490,10 +490,9 @@ public class ComputerUtilBlock { // enough and the new one would deal the remaining damage final int currentDamage = ComputerUtilCombat.totalDamageOfBlockers(attacker, blockGang); final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker); - final int absorbedDamage2 = blocker - .getEnoughDamageToKill(attacker.getNetCombatDamage(), attacker, true); + final int absorbedDamage2 = ComputerUtilCombat.getEnoughDamageToKill(blocker, attacker.getNetCombatDamage(), attacker, true); final int addedValue = CardFactoryUtil.evaluateCreature(blocker); - final int damageNeeded = attacker.getKillDamage() + final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); if ((damageNeeded > currentDamage || CombatUtil.needsBlockers(attacker) > blockGang.size()) && !(damageNeeded > currentDamage + additionalDamage) @@ -666,7 +665,7 @@ public class ComputerUtilBlock { // Try to use safe blockers first safeBlockers = ComputerUtilBlock.getSafeBlockers(ai, attacker, blockers, combat); for (final Card blocker : safeBlockers) { - final int damageNeeded = attacker.getKillDamage() + 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 @@ -690,7 +689,7 @@ public class ComputerUtilBlock { } for (final Card blocker : safeBlockers) { - final int damageNeeded = attacker.getKillDamage() + 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 the remaining damage diff --git a/src/main/java/forge/game/ai/ComputerUtilCombat.java b/src/main/java/forge/game/ai/ComputerUtilCombat.java index 72f3ff3e1b1..a6ba928882e 100644 --- a/src/main/java/forge/game/ai/ComputerUtilCombat.java +++ b/src/main/java/forge/game/ai/ComputerUtilCombat.java @@ -32,12 +32,14 @@ import forge.card.TriggerReplacementBase; import forge.card.abilityfactory.AbilityFactory; import forge.card.abilityfactory.ApiType; import forge.card.cardfactory.CardFactoryUtil; +import forge.card.replacement.ReplacementEffect; import forge.card.spellability.AbilityActivated; import forge.card.spellability.SpellAbility; import forge.card.staticability.StaticAbility; import forge.card.trigger.Trigger; import forge.card.trigger.TriggerHandler; import forge.card.trigger.TriggerType; +import forge.game.GameState; import forge.game.GlobalRuleChange; import forge.game.phase.Combat; import forge.game.phase.CombatUtil; @@ -124,9 +126,9 @@ public class ComputerUtilCombat { } damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, combat, false); if (!attacker.hasKeyword("Infect")) { - sum = attacked.predictDamage(damage, attacker, true); + sum = ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true); if (attacker.hasKeyword("Double Strike")) { - sum += attacked.predictDamage(damage, attacker, true); + sum += ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true); } } return sum; @@ -151,9 +153,9 @@ public class ComputerUtilCombat { int poison = 0; damage += ComputerUtilCombat.predictPowerBonusOfAttacker(attacker, null, null, false); if (attacker.hasKeyword("Infect")) { - poison += attacked.predictDamage(damage, attacker, true); + poison += ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true); if (attacker.hasKeyword("Double Strike")) { - poison += attacked.predictDamage(damage, attacker, true); + poison += ComputerUtilCombat.predictDamageTo(attacked, damage, attacker, true); } } if (attacker.hasKeyword("Poisonous") && (damage > 0)) { @@ -442,10 +444,10 @@ public class ComputerUtilCombat { } // consider static Damage Prevention - defenderDamage = attacker.predictDamage(defenderDamage, defender, true); + defenderDamage = predictDamageTo(attacker, defenderDamage, defender, true); if (defender.hasKeyword("Double Strike")) { - defenderDamage += attacker.predictDamage(defenderDamage, defender, true); + defenderDamage += predictDamageTo(attacker, defenderDamage, defender, true); } return defenderDamage; @@ -555,7 +557,7 @@ public class ComputerUtilCombat { } } - return ComputerUtilCombat.totalDamageOfBlockers(attacker, blockers) >= attacker.getKillDamage(); + return ComputerUtilCombat.totalDamageOfBlockers(attacker, blockers) >= ComputerUtilCombat.getDamageToKill(attacker); } // Will this trigger trigger? @@ -850,7 +852,7 @@ public class ComputerUtilCombat { // can't parse the number (X for example) continue; } - toughness -= defender.predictDamage(damage, 0, source, false); + toughness -= predictDamageTo(defender, damage, 0, source, false); continue; } @@ -1157,7 +1159,7 @@ public class ComputerUtilCombat { // can't parse the number (X for example) continue; } - toughness -= attacker.predictDamage(damage, 0, source, false); + toughness -= predictDamageTo(attacker, damage, 0, source, false); continue; } @@ -1409,12 +1411,12 @@ public class ComputerUtilCombat { } // consider Damage Prevention/Replacement - defenderDamage = attacker.predictDamage(defenderDamage, possibleAttackerPrevention, defender, true); - attackerDamage = defender.predictDamage(attackerDamage, possibleDefenderPrevention, attacker, true); + defenderDamage = predictDamageTo(attacker, defenderDamage, possibleAttackerPrevention, defender, true); + attackerDamage = predictDamageTo(defender, attackerDamage, possibleDefenderPrevention, attacker, true); - final int defenderLife = defender.getKillDamage() + final int defenderLife = ComputerUtilCombat.getDamageToKill(defender) + ComputerUtilCombat.predictToughnessBonusOfBlocker(attacker, defender, withoutAbilities); - final int attackerLife = attacker.getKillDamage() + final int attackerLife = ComputerUtilCombat.getDamageToKill(attacker) + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities); if (defender.hasKeyword("Double Strike")) { @@ -1522,7 +1524,7 @@ public class ComputerUtilCombat { if (flankingMagnitude >= defender.getNetDefense()) { return true; } - if ((flankingMagnitude >= defender.getKillDamage()) && !defender.hasKeyword("Indestructible")) { + if ((flankingMagnitude >= ComputerUtilCombat.getDamageToKill(defender)) && !defender.hasKeyword("Indestructible")) { return true; } } // flanking @@ -1559,20 +1561,20 @@ public class ComputerUtilCombat { } // consider Damage Prevention/Replacement - defenderDamage = attacker.predictDamage(defenderDamage, possibleAttackerPrevention, defender, true); - attackerDamage = defender.predictDamage(attackerDamage, possibleDefenderPrevention, attacker, true); + defenderDamage = predictDamageTo(attacker, defenderDamage, possibleAttackerPrevention, defender, true); + attackerDamage = predictDamageTo(defender, attackerDamage, possibleDefenderPrevention, attacker, true); if (combat != null) { for (Card atkr : combat.getAttackersBlockedBy(defender)) { if (!atkr.equals(attacker)) { - attackerDamage += defender.predictDamage(atkr.getNetCombatDamage(), 0, atkr, true); + attackerDamage += predictDamageTo(defender, atkr.getNetCombatDamage(), 0, atkr, true); } } } - final int defenderLife = defender.getKillDamage() + final int defenderLife = ComputerUtilCombat.getDamageToKill(defender) + ComputerUtilCombat.predictToughnessBonusOfBlocker(attacker, defender, withoutAbilities); - final int attackerLife = attacker.getKillDamage() + final int attackerLife = ComputerUtilCombat.getDamageToKill(attacker) + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, defender, combat, withoutAbilities); if (attacker.hasKeyword("Double Strike")) { @@ -1626,4 +1628,293 @@ public class ComputerUtilCombat { } // attacker no double strike return false; // should never arrive here } // canDestroyBlocker + + + /** + *

+ * distributeAIDamage. + *

+ * + * @param attacker + * a {@link forge.Card} object. + * @param block + * a {@link forge.CardList} object. + * @param damage + * a int. + */ + public static void distributeAIDamage(final Card attacker, final List block, int damage, Combat combat) { + final Card c = attacker; + + if (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") + || attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked.")) { + combat.addDefendingDamage(damage, attacker); + return; + } + + final boolean hasTrample = attacker.hasKeyword("Trample"); + + if (block.size() == 1) { + + final Card blocker = block.get(0); + + // trample + if (hasTrample) { + + int damageNeeded = 0; + + // TODO if the human can be killed distribute only the minimum + // of damage to the blocker + + damageNeeded = ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true); + + if (damageNeeded > damage) { + damageNeeded = Math.min(blocker.getLethalDamage(), damage); + } else { + damageNeeded = Math.max(blocker.getLethalDamage(), damageNeeded); + } + + final int trample = damage - damageNeeded; + + // If Extra trample damage, assign to defending + // player/planeswalker + if (0 < trample) { + combat.addDefendingDamage(trample, attacker); + } + + blocker.addAssignedDamage(damageNeeded, attacker); + } else { + blocker.addAssignedDamage(damage, attacker); + } + } // 1 blocker + else { + boolean killsAllBlockers = true; + // Does the attacker deal lethal damage to all blockers + //Blocking Order now determined after declare blockers + Card lastBlocker = null; + for (final Card b : block) { + final int enoughDamageToKill = ComputerUtilCombat.getEnoughDamageToKill(b, damage, attacker, true); + if (enoughDamageToKill <= damage) { + damage -= enoughDamageToKill; + final List cl = new ArrayList(); + cl.add(attacker); + + b.addAssignedDamage(enoughDamageToKill, c); + } else { + killsAllBlockers = false; + } + lastBlocker = b; + } // for + + if (killsAllBlockers && damage > 0) { + // if attacker has no trample, and there's damage left, assign the rest to the last blocker + if (!hasTrample && lastBlocker != null) { + lastBlocker.addAssignedDamage(damage, c); + damage = 0; + } else if (hasTrample) { + combat.addDefendingDamage(damage, c); + } + } + } + } // setAssignedDamage() + + + // how much damage is enough to kill the creature (for AI) + /** + *

+ * getEnoughDamageToKill. + *

+ * + * @param maxDamage + * a int. + * @param source + * a {@link forge.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + public final static int getEnoughDamageToKill(final Card c, final int maxDamage, final Card source, final boolean isCombat) { + return getEnoughDamageToKill(c, maxDamage, source, isCombat, false); + } + + /** + *

+ * getEnoughDamageToKill. + *

+ * + * @param maxDamage + * a int. + * @param source + * a {@link forge.Card} object. + * @param isCombat + * a boolean. + * @param noPrevention + * a boolean. + * @return a int. + */ + public static final int getEnoughDamageToKill(final Card c, final int maxDamage, final Card source, final boolean isCombat, + final boolean noPrevention) { + final int killDamage = ComputerUtilCombat.getDamageToKill(c); + + if (c.hasKeyword("Indestructible") || (c.getShield() > 0)) { + if (!(source.hasKeyword("Wither") || source.hasKeyword("Infect"))) { + return maxDamage + 1; + } + } else if (source.hasKeyword("Deathtouch")) { + for (int i = 1; i <= maxDamage; i++) { + if (noPrevention) { + if (c.staticReplaceDamage(i, source, isCombat) > 0) { + return i; + } + } else if (predictDamageTo(c, i, source, isCombat) > 0) { + return i; + } + } + } + + for (int i = 1; i <= maxDamage; i++) { + if (noPrevention) { + if (c.staticReplaceDamage(i, source, isCombat) >= killDamage) { + return i; + } + } else { + if (predictDamageTo(c, i, source, isCombat) >= killDamage) { + return i; + } + } + } + + return maxDamage + 1; + } + + // the amount of damage needed to kill the creature (for AI) + /** + *

+ * getKillDamage. + *

+ * + * @return a int. + */ + public final static int getDamageToKill(final Card c) { + int killDamage = c.getLethalDamage() + c.getPreventNextDamage(); + if ((killDamage > c.getPreventNextDamage()) + && c.hasStartOfKeyword("When CARDNAME is dealt damage, destroy it.")) { + killDamage = 1 + c.getPreventNextDamage(); + } + + return killDamage; + } + + + /** + *

+ * predictDamage. + *

+ * + * @param damage + * a int. + * @param source + * a {@link forge.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + + public final static int predictDamageTo(final Player target, final int damage, final Card source, final boolean isCombat) { + + final GameState game = Singletons.getModel().getGame(); + int restDamage = damage; + + restDamage = target.staticReplaceDamage(restDamage, source, isCombat); + + // Predict replacement effects + for (final Card ca : game.getCardsIn(ZoneType.Battlefield)) { + for (final ReplacementEffect re : ca.getReplacementEffects()) { + HashMap params = re.getMapParams(); + if (!"DamageDone".equals(params.get("Event")) || !params.containsKey("PreventionEffect")) { + continue; + } + if (params.containsKey("ValidSource") + && !source.isValid(params.get("ValidSource"), ca.getController(), ca)) { + continue; + } + if (params.containsKey("ValidTarget") + && !target.isValid(params.get("ValidTarget"), ca.getController(), ca)) { + continue; + } + if (params.containsKey("IsCombat")) { + if (params.get("IsCombat").equals("True")) { + if (!isCombat) { + continue; + } + } else { + if (isCombat) { + continue; + } + } + + } + return 0; + } + } + + restDamage = target.staticDamagePrevention(restDamage, source, isCombat); + + return restDamage; + } + + /** + *

+ * predictDamage. + *

+ * + * @param damage + * a int. + * @param source + * a {@link forge.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + // This function helps the AI calculate the actual amount of damage an + // effect would deal + public final static int predictDamageTo(final Card target, final int damage, final Card source, final boolean isCombat) { + + int restDamage = damage; + + restDamage = target.staticReplaceDamage(restDamage, source, isCombat); + restDamage = target.staticDamagePrevention(restDamage, source, isCombat); + + return restDamage; + } + + + // This function helps the AI calculate the actual amount of damage an + // effect would deal + /** + *

+ * predictDamage. + *

+ * + * @param damage + * a int. + * @param possiblePrevention + * a int. + * @param source + * a {@link forge.Card} object. + * @param isCombat + * a boolean. + * @return a int. + */ + public final static int predictDamageTo(final Card target, final int damage, final int possiblePrevention, final Card source, final boolean isCombat) { + + int restDamage = damage; + + restDamage = target.staticReplaceDamage(restDamage, source, isCombat); + restDamage = target.staticDamagePrevention(restDamage, possiblePrevention, source, isCombat); + + return restDamage; + } + } + + diff --git a/src/main/java/forge/game/ai/ComputerUtilCost.java b/src/main/java/forge/game/ai/ComputerUtilCost.java index 625ae61d16d..5ab8f8444e9 100644 --- a/src/main/java/forge/game/ai/ComputerUtilCost.java +++ b/src/main/java/forge/game/ai/ComputerUtilCost.java @@ -160,7 +160,7 @@ public class ComputerUtilCost { for (final CostPart part : cost.getCostParts()) { if (part instanceof CostDamage) { final CostDamage pay = (CostDamage) part; - int realDamage = ai.predictDamage(pay.convertAmount(), source, false); + int realDamage = ComputerUtilCombat.predictDamageTo(ai, pay.convertAmount(), source, false); if (ai.getLife() - realDamage < remainingLife && realDamage > 0 && !ai.cantLoseForZeroOrLessLife() && ai.canLoseLife()) { diff --git a/src/main/java/forge/game/phase/Combat.java b/src/main/java/forge/game/phase/Combat.java index fb9bbda9f0d..e67e7ca80ed 100644 --- a/src/main/java/forge/game/phase/Combat.java +++ b/src/main/java/forge/game/phase/Combat.java @@ -33,6 +33,7 @@ import forge.CardPredicates; import forge.GameEntity; import forge.Singletons; import forge.card.trigger.TriggerType; +import forge.game.ai.ComputerUtilCombat; import forge.game.event.BlockerAssignedEvent; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -290,8 +291,8 @@ public class Combat { final GameEntity ge = this.getDefenderByAttacker(source); if (ge instanceof Card) { - final Card pw = (Card) ge; - pw.addAssignedDamage(n, source); + final Card planeswalker = (Card) ge; + planeswalker.addAssignedDamage(n, source); return; } @@ -692,18 +693,16 @@ public class Combat { final int damage = blocker.getNetCombatDamage(); - if (attackers.size() == 0) { - // Just in case it was removed or something - } else { + if (!attackers.isEmpty()) { assignedDamage = true; - if (this.getAttackingPlayer().isComputer()) { // ai attacks + if (blocker.getController().isHuman()) { // who is defending matters if (attackers.size() > 1) { CMatchUI.SINGLETON_INSTANCE.assignDamage(blocker, attackers, damage, null); } else { attackers.get(0).addAssignedDamage(damage, blocker); } } else { // computer attacks - this.distributeAIDamage(blocker, attackers, damage); + ComputerUtilCombat.distributeAIDamage(blocker, attackers, damage, this); } } } @@ -742,8 +741,10 @@ public class Combat { if (blockers.size() == 0) { if (trampler || this.isUnblocked(attacker)) { this.addDefendingDamage(damageDealt, attacker); + } else { + // Else no damage can be dealt anywhere + continue; } - // Else no damage can be dealt anywhere } else { if (this.getAttackingPlayer().isHuman()) { // human attacks if (assignDamageAsIfNotBlocked(attacker)) { @@ -756,7 +757,7 @@ public class Combat { } } } else { // computer attacks - this.distributeAIDamage(attacker, blockers, damageDealt); + ComputerUtilCombat.distributeAIDamage(attacker, blockers, damageDealt, this); } } // if !hasFirstStrike ... } // for @@ -769,93 +770,6 @@ public class Combat { return assignedDamage; } - /** - *

- * distributeAIDamage. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param block - * a {@link forge.CardList} object. - * @param damage - * a int. - */ - private void distributeAIDamage(final Card attacker, final List block, int damage) { - final Card c = attacker; - - if (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") - || attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked.")) { - this.addDefendingDamage(damage, attacker); - return; - } - - final boolean hasTrample = attacker.hasKeyword("Trample"); - - if (block.size() == 1) { - - final Card blocker = block.get(0); - - // trample - if (hasTrample) { - - int damageNeeded = 0; - - // TODO if the human can be killed distribute only the minimum - // of damage to the blocker - - damageNeeded = blocker.getEnoughDamageToKill(damage, attacker, true); - - if (damageNeeded > damage) { - damageNeeded = Math.min(blocker.getLethalDamage(), damage); - } else { - damageNeeded = Math.max(blocker.getLethalDamage(), damageNeeded); - } - - final int trample = damage - damageNeeded; - - // If Extra trample damage, assign to defending - // player/planeswalker - if (0 < trample) { - this.addDefendingDamage(trample, attacker); - } - - blocker.addAssignedDamage(damageNeeded, attacker); - } else { - blocker.addAssignedDamage(damage, attacker); - } - } // 1 blocker - else { - boolean killsAllBlockers = true; - // Does the attacker deal lethal damage to all blockers - //Blocking Order now determined after declare blockers - Card lastBlocker = null; - for (final Card b : block) { - final int enoughDamageToKill = b.getEnoughDamageToKill(damage, attacker, true); - if (enoughDamageToKill <= damage) { - damage -= enoughDamageToKill; - final List cl = new ArrayList(); - cl.add(attacker); - - b.addAssignedDamage(enoughDamageToKill, c); - } else { - killsAllBlockers = false; - } - lastBlocker = b; - } // for - - if (killsAllBlockers && damage > 0) { - // if attacker has no trample, and there's damage left, assign the rest to the last blocker - if (!hasTrample && lastBlocker != null) { - lastBlocker.addAssignedDamage(damage, c); - damage = 0; - } else if (hasTrample) { - this.addDefendingDamage(damage, c); - } - } - } - } // setAssignedDamage() - /** *

* dealAssignedDamage. diff --git a/src/main/java/forge/game/phase/Upkeep.java b/src/main/java/forge/game/phase/Upkeep.java index 0011936202b..53248f26d18 100644 --- a/src/main/java/forge/game/phase/Upkeep.java +++ b/src/main/java/forge/game/phase/Upkeep.java @@ -44,6 +44,7 @@ import forge.control.input.InputSelectManyCards; import forge.game.GameActionUtil; import forge.game.GameState; import forge.game.ai.ComputerUtil; +import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilMana; import forge.game.player.AIPlayer; @@ -401,7 +402,7 @@ public class Upkeep extends Phase { GameActionUtil.payManaDuringAbilityResolve(sb.toString(), upkeepCost, paidCommand, unpaidCommand); } else { // computers if (ComputerUtilCost.canPayCost(aiPaid, controller) - && (controller.predictDamage(upkeepDamage, c, false) > 0)) { + && (ComputerUtilCombat.predictDamageTo(controller, upkeepDamage, c, false) > 0)) { ComputerUtil.playNoStack((AIPlayer)controller, aiPaid, game); } else { controller.addDamage(upkeepDamage, c); diff --git a/src/main/java/forge/game/player/Player.java b/src/main/java/forge/game/player/Player.java index 0df84b34d05..d9ee295b5d9 100644 --- a/src/main/java/forge/game/player/Player.java +++ b/src/main/java/forge/game/player/Player.java @@ -47,7 +47,6 @@ import forge.card.SpellManaCost; import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.mana.ManaPool; -import forge.card.replacement.ReplacementEffect; import forge.card.replacement.ReplacementResult; import forge.card.spellability.Ability; import forge.card.spellability.Spell; @@ -653,62 +652,6 @@ public abstract class Player extends GameEntity implements Comparable { return true; } - /** - *

- * predictDamage. - *

- * - * @param damage - * a int. - * @param source - * a {@link forge.Card} object. - * @param isCombat - * a boolean. - * @return a int. - */ - @Override - public final int predictDamage(final int damage, final Card source, final boolean isCombat) { - - int restDamage = damage; - - restDamage = this.staticReplaceDamage(restDamage, source, isCombat); - - // Predict replacement effects - for (final Card ca : game.getCardsIn(ZoneType.Battlefield)) { - for (final ReplacementEffect re : ca.getReplacementEffects()) { - HashMap params = re.getMapParams(); - if (!"DamageDone".equals(params.get("Event")) || !params.containsKey("PreventionEffect")) { - continue; - } - if (params.containsKey("ValidSource") - && !source.isValid(params.get("ValidSource"), ca.getController(), ca)) { - continue; - } - if (params.containsKey("ValidTarget") - && !this.isValid(params.get("ValidTarget"), ca.getController(), ca)) { - continue; - } - if (params.containsKey("IsCombat")) { - if (params.get("IsCombat").equals("True")) { - if (!isCombat) { - continue; - } - } else { - if (isCombat) { - continue; - } - } - - } - return 0; - } - } - - restDamage = this.staticDamagePrevention(restDamage, source, isCombat); - - return restDamage; - } - // This should be also usable by the AI to forecast an effect (so it must // not change the game state) /**