diff --git a/src/main/java/forge/game/ai/AiAttackController.java b/src/main/java/forge/game/ai/AiAttackController.java index b17c0f0dd74..d77b2a14065 100644 --- a/src/main/java/forge/game/ai/AiAttackController.java +++ b/src/main/java/forge/game/ai/AiAttackController.java @@ -460,7 +460,7 @@ public class AiAttackController { } else { // 1. assault the opponent if you can kill him if (bAssault) { - return c.getDefendingPlayers().get(0); + return getDefendingPlayers(c).get(0); } // 2. attack planeswalkers List pwDefending = c.getDefendingPlaneswalkers(); @@ -475,6 +475,17 @@ public class AiAttackController { final boolean LOG_AI_ATTACKS = false; + public final List getDefendingPlayers(Combat combat) { + final List defending = new ArrayList(); + for (final GameEntity o : combat.getDefenders()) { + if (o instanceof Player) { + defending.add((Player) o); + } + } + return defending; + } + + /** *

* Getter for the field attackers. @@ -806,7 +817,7 @@ public class AiAttackController { } } if (!found) { - defender = combat.getDefendingPlayers().get(0); + defender = getDefendingPlayers(combat).get(0); } } } diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index 752ed9dc089..173da802e3d 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -984,7 +984,7 @@ public class ComputerUtil { Combat combat = new Combat(ai.getOpponent()); List attackers = ai.getOpponent().getCreaturesInPlay(); for (Card att : attackers) { - if (CombatUtil.canAttackNextTurn(att)) { + if (CombatUtil.canAttackNextTurn(att, ai)) { combat.addAttacker(att, att.getController().getOpponent()); } } diff --git a/src/main/java/forge/game/phase/Combat.java b/src/main/java/forge/game/phase/Combat.java index 4e28ba52d69..61f1d8e8351 100644 --- a/src/main/java/forge/game/phase/Combat.java +++ b/src/main/java/forge/game/phase/Combat.java @@ -23,6 +23,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; + +import org.apache.commons.lang3.tuple.Pair; + import com.google.common.collect.Lists; import forge.Card; @@ -46,7 +49,7 @@ import forge.util.maps.MapOfLists; * @version $Id$ */ public class Combat { - private final Player attackerPlayer; + private final Player playerWhoAttacks; // Defenders, as they are attacked by hostile forces private final MapOfLists attackedEntities = new HashMapOfLists(CollectionSuppliers.arrayLists()); // Blockers to stop the hostile invaders @@ -55,15 +58,15 @@ public class Combat { private final HashMap defendingDamageMap = new HashMap(); - private Map> orderBlockerDamageAssignment = new HashMap>(); - private Map> orderAttackerDamageAssignment = new HashMap>(); + private Map> attackersOrderedForDamageAssignment = new HashMap>(); + private Map> blockersOrderedForDamageAssignment = new HashMap>(); public Combat(Player attacker) { - attackerPlayer = attacker; + playerWhoAttacks = attacker; // Create keys for all possible attack targets - for (Player defender : attackerPlayer.getOpponents()) { + for (Player defender : playerWhoAttacks.getOpponents()) { this.attackedEntities.ensureCollectionFor(defender); List planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); for (final Card pw : planeswalkers) { @@ -73,31 +76,13 @@ public class Combat { } public final Player getAttackingPlayer() { - return this.attackerPlayer; - } - - public final boolean isCombat() { - for(Collection abs : attackedEntities.values()) { - if(!abs.isEmpty()) - return true; - } - return false; + return this.playerWhoAttacks; } public final List getDefenders() { return Lists.newArrayList(attackedEntities.keySet()); } - public final List getDefendingPlayers() { - final List defending = new ArrayList(); - for (final GameEntity o : attackedEntities.keySet()) { - if (o instanceof Player) { - defending.add((Player) o); - } - } - return defending; - } - public final List getDefendingPlaneswalkers() { final List pwDefending = new ArrayList(); for (final GameEntity o : attackedEntities.keySet()) { @@ -108,24 +93,6 @@ public class Combat { return pwDefending; } - // Damage to whatever was protected there. - public final void addDefendingDamage(final int n, final Card source) { - final GameEntity ge = this.getDefenderByAttacker(source); - - if (ge instanceof Card) { - final Card planeswalker = (Card) ge; - planeswalker.addAssignedDamage(n, source); - - return; - } - - if (!this.defendingDamageMap.containsKey(source)) { - this.defendingDamageMap.put(source, n); - } else { - this.defendingDamageMap.put(source, this.defendingDamageMap.get(source) + n); - } - } - public final List getAttackingBandsOf(GameEntity defender) { return Lists.newArrayList(attackedEntities.get(defender)); } @@ -149,9 +116,7 @@ public class Combat { System.out.println("Trying to add Attacker " + c + " to missing defender " + defender); return; } - - - + if (band == null || !attackersOfDefender.contains(band)) { band = new AttackingBand(c, defender); attackersOfDefender.add(band); @@ -170,7 +135,7 @@ public class Combat { return null; } - private final GameEntity getDefenderByAttacker(final AttackingBand c) { + public final GameEntity getDefenderByAttacker(final AttackingBand c) { for(Entry> e : attackedEntities.entrySet()) { for(AttackingBand ab : e.getValue()) { if ( ab == c ) @@ -217,12 +182,6 @@ public class Combat { return ab != null; } - /** - * TODO: Write javadoc for this method. - * @param card - * @param currentDefender - * @return - */ public boolean isAttacking(Card card, GameEntity defender) { for(Entry> ee : attackedEntities.entrySet()) for(AttackingBand ab : ee.getValue()) @@ -252,6 +211,7 @@ public class Combat { } + // Some cards in Alpha may UNBLOCK an attacker, so second parameter is not always-true public final void setBlocked(final Card attacker, boolean value) { getBandOfAttacker(attacker).setBlocked(value); // called by Curtain of Light, Dazzling Beauty, Trap Runner } @@ -314,24 +274,51 @@ public class Combat { return getDefenderPlayerByAttacker(attacker); } - public void setAttackerDamageAssignmentOrder(final Card attacker, final List blockers) { - this.orderAttackerDamageAssignment.put(attacker, blockers); - } + /** If there are multiple blockers, the Attacker declares the Assignment Order */ + public void orderBlockersForDamageAssignment() { - public void setBlockerDamageAssignmentOrder(final Card blocker, final List attackers) { - this.orderBlockerDamageAssignment.put(blocker, attackers); - } + for(Collection abs : attackedEntities.values()) + for (final AttackingBand band : abs) { + if (band.isEmpty()) continue; // there should not be empty bands + Collection blockers = blockedBands.get(band); + if ( blockers == null || blockers.isEmpty() ) + continue; + + for(Card attacker : band.getAttackers()) { + List orderedBlockers = blockers.size() <= 1 + ? Lists.newArrayList(blockers) + : playerWhoAttacks.getController().orderBlockers(attacker, (List)blockers); // we know there's a list + // Damage Ordering needs to take cards like Melee into account, is that happening? + blockersOrderedForDamageAssignment.put(attacker, orderedBlockers); + } + } + } + + public void orderAttackersForDamageAssignment() { + // If there are multiple blockers, the Attacker declares the Assignment Order + for (final Card blocker : getAllBlockers()) { + List attackers = getAttackersBlockedBy(blocker); + // They need a reverse map here: Blocker => List + + Player blockerCtrl = blocker.getController(); + List orderedAttacker = attackers.size() <= 1 ? attackers : blockerCtrl.getController().orderAttackers(blocker, attackers); + + // Damage Ordering needs to take cards like Melee into account, is that happening? + attackersOrderedForDamageAssignment.put(blocker, orderedAttacker); + } + } + // removes references to this attacker from all indices and orders private void unregisterAttacker(final Card c, AttackingBand ab) { - orderAttackerDamageAssignment.remove(c); + blockersOrderedForDamageAssignment.remove(c); Collection blockers = blockedBands.get(ab); if ( blockers != null ) { for (Card b : blockers) { // Clear removed attacker from assignment order - if (this.orderBlockerDamageAssignment.containsKey(b)) { - this.orderBlockerDamageAssignment.get(b).remove(c); + if (this.attackersOrderedForDamageAssignment.containsKey(b)) { + this.attackersOrderedForDamageAssignment.get(b).remove(c); } } } @@ -340,21 +327,31 @@ public class Combat { // removes references to this defender from all indices and orders private void unregisterDefender(final Card c, AttackingBand bandBeingBlocked) { - this.orderBlockerDamageAssignment.remove(c); + this.attackersOrderedForDamageAssignment.remove(c); for(Card atk : bandBeingBlocked.getAttackers()) { - if (this.orderAttackerDamageAssignment.containsKey(atk)) { - this.orderAttackerDamageAssignment.get(atk).remove(c); + if (this.blockersOrderedForDamageAssignment.containsKey(atk)) { + this.blockersOrderedForDamageAssignment.get(atk).remove(c); } } } + + private void removeAttackerFromBand(AttackingBand ab, Card c) { + ab.removeAttacker(c); + + // removed + if (ab.isEmpty()) { + blockedBands.remove(ab); + for(Collection abs: attackedEntities.values()) + abs.remove(ab); + } + } // remove a combatant whose side is unknown public final void removeFromCombat(final Card c) { AttackingBand ab = getBandOfAttacker(c); if (ab != null) { unregisterAttacker(c, ab); - ab.removeAttacker(c); - // no need to remove empty bands + removeAttackerFromBand(ab, c); } // if not found in attackers, look for this card in blockers @@ -369,6 +366,7 @@ public class Combat { public final void removeAbsentCombatants() { // iterate all attackers and remove them + List> toRemoveAtk = new ArrayList<>(); for(Entry> ee : attackedEntities.entrySet()) { for(AttackingBand ab : ee.getValue()) { List atk = ab.getAttackers(); @@ -376,11 +374,15 @@ public class Combat { Card c = atk.get(i); if ( !c.isInPlay() ) { unregisterAttacker(c, ab); - ab.removeAttacker(c); + toRemoveAtk.add(Pair.of(ab, c)); } } } } + // remove as a separate step to avoid modifications with iterator open in for-loop + for(Pair pair : toRemoveAtk) { + removeAttackerFromBand(pair.getKey(), pair.getValue()); + } Collection toRemove = Lists.newArrayList(); for(Entry> be : blockedBands.entrySet()) { @@ -423,7 +425,7 @@ public class Combat { for (final Card blocker : blockers) { if (blocker.hasDoubleStrike() || blocker.hasFirstStrike() == firstStrikeDamage) { - List attackers = this.orderBlockerDamageAssignment.get(blocker); + List attackers = this.attackersOrderedForDamageAssignment.get(blocker); final int damage = blocker.getNetCombatDamage(); @@ -450,7 +452,7 @@ public class Combat { private final boolean assignAttackersDamage(boolean firstStrikeDamage) { // Assign damage by Attackers this.defendingDamageMap.clear(); // this should really happen in deal damage - List blockers = null; + List orderedBlockers = null; final List attackers = this.getAttackers(); boolean assignedDamage = false; for (final Card attacker : attackers) { @@ -468,10 +470,10 @@ public class Combat { AttackingBand band = this.getBandOfAttacker(attacker); boolean trampler = attacker.hasKeyword("Trample"); - blockers = this.orderAttackerDamageAssignment.get(attacker); + orderedBlockers = this.blockersOrderedForDamageAssignment.get(attacker); assignedDamage = true; // If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender - if (blockers == null || blockers.isEmpty()) { + if (orderedBlockers == null || orderedBlockers.isEmpty()) { if (trampler || !band.isBlocked()) { this.addDefendingDamage(damageDealt, attacker); } // No damage happens if blocked but no blockers left @@ -482,11 +484,11 @@ public class Combat { // It allows the defending player to assign damage instead of the attacking player if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) { assigningPlayer = (Player)defender; - } else if ( AttackingBand.isValidBand(blockers, true)){ - assigningPlayer = blockers.get(0).getController(); + } else if ( AttackingBand.isValidBand(orderedBlockers, true)){ + assigningPlayer = orderedBlockers.get(0).getController(); } - Map map = assigningPlayer.getController().assignCombatDamage(attacker, blockers, damageDealt, defender, this.getAttackingPlayer() != assigningPlayer); + Map map = assigningPlayer.getController().assignCombatDamage(attacker, orderedBlockers, damageDealt, defender, this.getAttackingPlayer() != assigningPlayer); for (Entry dt : map.entrySet()) { if( dt.getKey() == null) { if (dt.getValue() > 0) @@ -502,6 +504,24 @@ public class Combat { return assignedDamage; } + // Damage to whatever was protected there. + private final void addDefendingDamage(final int n, final Card source) { + final GameEntity ge = this.getDefenderByAttacker(source); + + if (ge instanceof Card) { + final Card planeswalker = (Card) ge; + planeswalker.addAssignedDamage(n, source); + + return; + } + + if (!this.defendingDamageMap.containsKey(source)) { + this.defendingDamageMap.put(source, n); + } else { + this.defendingDamageMap.put(source, this.defendingDamageMap.get(source) + n); + } + } + public final boolean assignCombatDamage(boolean firstStrikeDamage) { boolean assignedDamage = assignAttackersDamage(firstStrikeDamage); assignedDamage |= assignBlockersDamage(firstStrikeDamage); diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index 1c31fea5215..59b57036ad5 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -401,46 +401,6 @@ public class CombatUtil { return null; } - public static void orderMultipleBlockers(final Combat combat) { - // If there are multiple blockers, the Attacker declares the Assignment Order - final Player player = combat.getAttackingPlayer(); - for (final AttackingBand band : combat.getAttackingBands()) { - List attackers = band.getAttackers(); - if (attackers.isEmpty()) { - continue; - } - - List blockers = combat.getBlockers(band); - for(Card attacker : attackers) { - List orderedBlockers = null; - if (blockers.size() <= 1) { - orderedBlockers = blockers; - } else { - // Damage Ordering needs to take cards like Melee into account, is that happening? - orderedBlockers = player.getController().orderBlockers(attacker, blockers); - } - combat.setAttackerDamageAssignmentOrder(attacker, orderedBlockers); - } - } - } - - public static void orderBlockingMultipleAttackers(final Combat combat) { - // If there are multiple blockers, the Attacker declares the Assignment Order - for (final Card blocker : combat.getAllBlockers()) { - List attackers = combat.getAttackersBlockedBy(blocker); - List orderedAttacker = null; - - if (attackers.size() <= 1) { - orderedAttacker = attackers; - } else { - // Damage Ordering needs to take cards like Melee into account, is that happening? - orderedAttacker = blocker.getController().getController().orderAttackers(blocker, attackers); - } - - combat.setBlockerDamageAssignmentOrder(blocker, orderedAttacker); - } - } - // can the blocker block an attacker with a lure effect? /** *

diff --git a/src/main/java/forge/game/phase/PhaseHandler.java b/src/main/java/forge/game/phase/PhaseHandler.java index 3e02b3a1203..a1adc33a2bb 100644 --- a/src/main/java/forge/game/phase/PhaseHandler.java +++ b/src/main/java/forge/game/phase/PhaseHandler.java @@ -527,8 +527,9 @@ public class PhaseHandler implements java.io.Serializable { if ( game.isGameOver() ) // they just like to close window at any moment return; } while(p != playerTurn); - CombatUtil.orderMultipleBlockers(combat); - CombatUtil.orderBlockingMultipleAttackers(combat); + + combat.orderBlockersForDamageAssignment(); + combat.orderAttackersForDamageAssignment(); combat.removeAbsentCombatants();