package mage.player.ai.util; import mage.abilities.keyword.DoubleStrikeAbility; import mage.abilities.keyword.InfectAbility; import mage.counters.CounterType; import mage.game.Game; import mage.game.combat.Combat; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.turn.CombatDamageStep; import mage.game.turn.EndOfCombatStep; import mage.game.turn.FirstCombatDamageStep; import mage.game.turn.Step; import mage.players.Player; import org.apache.log4j.Logger; import java.util.*; /** * Base helper methods for combat. * * @author noxx */ public final class CombatUtil { private static final List<Permanent> emptyList = new ArrayList<>(); private static final Logger log = Logger.getLogger(CombatUtil.class); private CombatUtil() { } public static List<Permanent> canKillOpponent(Game game, List<Permanent> attackersList, List<Permanent> blockersList, Player defender) { List<Permanent> blockableAttackers = new ArrayList<>(blockersList); List<Permanent> unblockableAttackers = new ArrayList<>(); for (Permanent attacker : attackersList) { if (!canBeBlocked(game, attacker, blockersList)) { unblockableAttackers.add(attacker); blockableAttackers.remove(attacker); } } sortByPower(blockableAttackers, true); // imagine that most powerful will be blocked as 1-vs-1 List<Permanent> attackersThatWontBeBlocked = new ArrayList<>(blockableAttackers); for (int i = 0; (i < blockersList.size() && i < blockableAttackers.size()); i++) { attackersThatWontBeBlocked.remove(blockableAttackers.get(i)); } attackersThatWontBeBlocked.addAll(unblockableAttackers); // now count if it is possible to win the game by this attack using unblockable attackers and // those attackers that won't be blocked for sure (as player will block other creatures) if (sumDamage(attackersThatWontBeBlocked, defender) >= defender.getLife() && defender.isLifeTotalCanChange() && defender.canLose(game) && defender.getLife() > 0) { blockableAttackers.addAll(unblockableAttackers); return blockableAttackers; } if (sumPoisonDamage(attackersThatWontBeBlocked, defender) >= 10 - defender.getCounters().getCount(CounterType.POISON)) { blockableAttackers.addAll(unblockableAttackers); return blockableAttackers; } return emptyList; } /** * Checks that attacker can be blocked by any blocker from the list. * * @param game Game * @param attacker Attacker to check * @param blockersList Blockers to try to block the attacker with. * @return true if attacker can be blocked by any blocker */ public static boolean canBeBlocked(Game game, Permanent attacker, List<Permanent> blockersList) { for (Permanent blocker : blockersList) { if (blocker.canBlock(attacker.getId(), game)) { return true; } } return false; } public static void sortByPower(List<Permanent> permanents, final boolean ascending) { Collections.sort(permanents, new Comparator<Permanent>() { @Override public int compare(Permanent o1, Permanent o2) { if (ascending) { return o2.getPower().getValue() - o1.getPower().getValue(); } else { return o1.getPower().getValue() - o2.getPower().getValue(); } } }); } public static Permanent getWorstCreature(List<Permanent> creatures) { if (creatures.isEmpty()) { return null; } Collections.sort(creatures, new Comparator<Permanent>() { @Override public int compare(Permanent o1, Permanent o2) { return o2.getPower().getValue() - o1.getPower().getValue(); } }); return creatures.get(0); } private static int sumDamage(List<Permanent> attackersThatWontBeBlocked, Player defender) { int damage = 0; for (Permanent attacker : attackersThatWontBeBlocked) { if (!attacker.getAbilities().contains(InfectAbility.getInstance())) { damage += attacker.getPower().getValue(); if (attacker.getAbilities().contains(DoubleStrikeAbility.getInstance())) { damage += attacker.getPower().getValue(); } } } return damage; } private static int sumPoisonDamage(List<Permanent> attackersThatWontBeBlocked, Player defender) { int damage = 0; for (Permanent attacker : attackersThatWontBeBlocked) { if (attacker.getAbilities().contains(InfectAbility.getInstance())) { damage += attacker.getPower().getValue(); if (attacker.getAbilities().contains(DoubleStrikeAbility.getInstance())) { damage += attacker.getPower().getValue(); } } } return damage; } public static void handleExalted() { } /** * Determines what blockers from the list can block the attacker * * @param game Game * @param attacker Attacker to check * @param blockersList Blockers to try to block the attacker with. * @return true if attacker can be blocked by any blocker */ public static List<Permanent> getPossibleBlockers(Game game, Permanent attacker, List<Permanent> blockersList) { List<Permanent> canBlock = new ArrayList<>(); for (Permanent blocker : blockersList) { if (blocker.canBlock(attacker.getId(), game)) { canBlock.add(blocker); } } return canBlock; } public static CombatInfo blockWithGoodTrade(Game game, List<Permanent> attackers, List<Permanent> blockers) { UUID attackerId = game.getCombat().getAttackerId(); UUID defenderId = game.getCombat().getDefenders().iterator().next(); if (attackerId == null || defenderId == null) { log.warn("Couldn't find attacker or defender: " + attackerId + ' ' + defenderId); return new CombatInfo(); } CombatInfo combatInfo = new CombatInfo(); for (Permanent attacker : attackers) { //TODO: handle attackers with "can't be blocked except" List<Permanent> possibleBlockers = getPossibleBlockers(game, attacker, blockers); List<Permanent> survivedBlockers = getBlockersThatWillSurvive(game, attackerId, defenderId, attacker, possibleBlockers); if (!survivedBlockers.isEmpty()) { Permanent blocker = getWorstCreature(survivedBlockers); combatInfo.addPair(attacker, blocker); blockers.remove(blocker); } if (blockers.isEmpty()) { break; } } return combatInfo; } private static List<Permanent> getBlockersThatWillSurvive(Game game, UUID attackerId, UUID defenderId, Permanent attacker, List<Permanent> possibleBlockers) { List<Permanent> blockers = new ArrayList<>(); for (Permanent blocker : possibleBlockers) { SurviveInfo info = willItSurvive(game, attackerId, defenderId, attacker, blocker); //if (info.isAttackerDied() && !info.isBlockerDied()) { if (info != null) { if (info.isAttackerDied()) { blockers.add(blocker); } else if (!info.isBlockerDied()) { blockers.add(blocker); } } } return blockers; } public static SurviveInfo willItSurvive(Game game, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker, Permanent blocker) { Game sim = game.copy(); Combat combat = sim.getCombat(); combat.setAttacker(attackingPlayerId); combat.setDefenders(sim); if (blocker == null || attacker == null || sim.getPlayer(defendingPlayerId) == null) { return null; } sim.getPlayer(defendingPlayerId).declareBlocker(defendingPlayerId, blocker.getId(), attacker.getId(), sim); sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defendingPlayerId, defendingPlayerId)); sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); sim.applyEffects(); } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); simulateStep(sim, new FirstCombatDamageStep()); simulateStep(sim, new CombatDamageStep()); simulateStep(sim, new EndOfCombatStep()); // The following commented out call produces random freezes. //sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); sim.applyEffects(); } return new SurviveInfo(!sim.getBattlefield().containsPermanent(attacker.getId()), !sim.getBattlefield().containsPermanent(blocker.getId())); } public static SurviveInfo getCombatInfo(Game game, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker) { Game sim = game.copy(); Combat combat = sim.getCombat(); combat.setAttacker(attackingPlayerId); combat.setDefenders(sim); UUID defenderId = sim.getCombat().getDefenders().iterator().next(); boolean triggered = false; sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defendingPlayerId, defendingPlayerId)); sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { triggered = true; sim.getStack().resolve(sim); sim.applyEffects(); } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); simulateStep(sim, new FirstCombatDamageStep()); simulateStep(sim, new CombatDamageStep()); simulateStep(sim, new EndOfCombatStep()); // The following commented out call produces random freezes. //sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { triggered = true; sim.getStack().resolve(sim); sim.applyEffects(); } return new SurviveInfo(!sim.getBattlefield().containsPermanent(attacker.getId()), false, sim.getPlayer(defenderId), triggered); } protected static void simulateStep(Game game, Step step) { game.getPhase().setStep(step); if (!step.skipStep(game, game.getActivePlayerId())) { step.beginStep(game, game.getActivePlayerId()); // The following commented out call produces random freezes. //game.checkStateAndTriggered(); while (!game.getStack().isEmpty()) { game.getStack().resolve(game); game.applyEffects(); } step.endStep(game, game.getActivePlayerId()); } } public static boolean canBlock(Game game, Permanent blocker) { boolean canBlock = true; if (!blocker.isTapped()) { try { canBlock = blocker.canBlock(null, game); } catch (Exception e) { //e.printStackTrace(); } } return canBlock; } public static CombatInfo blockWithGoodTrade2(Game game, List<Permanent> attackers, List<Permanent> blockers) { UUID attackerId = game.getCombat().getAttackerId(); UUID defenderId = game.getCombat().getDefenders().iterator().next(); if (attackerId == null || defenderId == null) { log.warn("Couldn't find attacker or defender: " + attackerId + ' ' + defenderId); return new CombatInfo(); } CombatInfo combatInfo = new CombatInfo(); for (Permanent attacker : attackers) { //TODO: handle attackers with "can't be blocked except" List<Permanent> possibleBlockers = getPossibleBlockers(game, attacker, blockers); List<Permanent> survivedBlockers = getBlockersThatWillSurvive2(game, attackerId, defenderId, attacker, possibleBlockers); if (!survivedBlockers.isEmpty()) { Permanent blocker = getWorstCreature(survivedBlockers); combatInfo.addPair(attacker, blocker); blockers.remove(blocker); } if (blockers.isEmpty()) { break; } } return combatInfo; } private static List<Permanent> getBlockersThatWillSurvive2(Game game, UUID attackerId, UUID defenderId, Permanent attacker, List<Permanent> possibleBlockers) { List<Permanent> blockers = new ArrayList<>(); for (Permanent blocker : possibleBlockers) { SurviveInfo info = willItSurvive2(game, attackerId, defenderId, attacker, blocker); //if (info.isAttackerDied() && !info.isBlockerDied()) { if (info != null) { if (info.isAttackerDied()) { blockers.add(blocker); } else if (!info.isBlockerDied()) { blockers.add(blocker); } } } return blockers; } public static SurviveInfo willItSurvive2(Game game, UUID attackingPlayerId, UUID defendingPlayerId, Permanent attacker, Permanent blocker) { Game sim = game.copy(); Combat combat = sim.getCombat(); combat.setAttacker(attackingPlayerId); combat.setDefenders(sim); if (blocker == null || attacker == null || sim.getPlayer(defendingPlayerId) == null) { return null; } if (attacker.getPower().getValue() >= blocker.getToughness().getValue()) { sim.getBattlefield().removePermanent(blocker.getId()); } if (attacker.getToughness().getValue() <= blocker.getPower().getValue()) { sim.getBattlefield().removePermanent(attacker.getId()); } /* sim.getPlayer(defendingPlayerId).declareBlocker(blocker.getId(), attacker.getId(), sim); sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defendingPlayerId, defendingPlayerId)); sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); sim.applyEffects(); } sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId())); simulateStep(sim, new FirstCombatDamageStep()); simulateStep(sim, new CombatDamageStep()); simulateStep(sim, new EndOfCombatStep()); // The following commented out call produces random freezes. //sim.checkStateAndTriggered(); while (!sim.getStack().isEmpty()) { sim.getStack().resolve(sim); sim.applyEffects(); } */ return new SurviveInfo(!sim.getBattlefield().containsPermanent(attacker.getId()), !sim.getBattlefield().containsPermanent(blocker.getId())); } }