diff --git a/.gitattributes b/.gitattributes index c11242b1d10..291efde6a81 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14405,6 +14405,7 @@ src/main/java/forge/game/ai/ComputerUtilCard.java -text src/main/java/forge/game/ai/ComputerUtilCombat.java -text src/main/java/forge/game/ai/ComputerUtilCost.java -text src/main/java/forge/game/ai/ComputerUtilMana.java -text +src/main/java/forge/game/combat/AttackingBand.java -text src/main/java/forge/game/event/GameEvent.java -text src/main/java/forge/game/event/GameEventAnteCardsSelected.java -text src/main/java/forge/game/event/GameEventAttackersDeclared.java -text diff --git a/src/main/java/forge/card/ability/ai/ChangeZoneAi.java b/src/main/java/forge/card/ability/ai/ChangeZoneAi.java index c45c60cab46..f380914d956 100644 --- a/src/main/java/forge/card/ability/ai/ChangeZoneAi.java +++ b/src/main/java/forge/card/ability/ai/ChangeZoneAi.java @@ -199,8 +199,7 @@ public class ChangeZoneAi extends SpellAbilityAi { if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) { return false; } - List attackers = new ArrayList(); - attackers.addAll(ai.getGame().getCombat().getUnblockedAttackers()); + List attackers = ai.getGame().getCombat().getUnblockedAttackers(); boolean lowerCMC = false; for (Card attacker : attackers) { if (attacker.getCMC() < source.getCMC()) { @@ -499,6 +498,7 @@ public class ChangeZoneAi extends SpellAbilityAi { * @return Card */ private static Card chooseCreature(final Player ai, List list) { + // Creating a new combat for testing purposes. Combat combat = new Combat(); combat.initiatePossibleDefenders(ai); List attackers = ai.getOpponent().getCreaturesInPlay(); diff --git a/src/main/java/forge/card/ability/ai/ProtectAi.java b/src/main/java/forge/card/ability/ai/ProtectAi.java index 2c7cf7b0d35..34b0b7c59bf 100644 --- a/src/main/java/forge/card/ability/ai/ProtectAi.java +++ b/src/main/java/forge/card/ability/ai/ProtectAi.java @@ -18,6 +18,7 @@ import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; +import forge.game.phase.Combat; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -68,6 +69,7 @@ public class ProtectAi extends SpellAbilityAi { private static List getProtectCreatures(final Player ai, final SpellAbility sa) { final ArrayList gains = AbilityUtils.getProtectionList(sa); final Game game = ai.getGame(); + final Combat combat = game.getCombat(); List list = ai.getCreaturesInPlay(); list = CardLists.filter(list, new Predicate() { @@ -97,9 +99,10 @@ public class ProtectAi extends SpellAbilityAi { } // is the creature in blocked and the blocker would survive - if (game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) - && game.getCombat().isAttacking(c) && game.getCombat().isBlocked(c) - && ComputerUtilCombat.blockerWouldBeDestroyed(ai, game.getCombat().getBlockers(c).get(0))) { + // TODO Potential NPE here if no blockers are actually left + if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS) + && combat.isAttacking(c) && game.getCombat().isBlocked(c) + && ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0))) { return true; } diff --git a/src/main/java/forge/card/ability/ai/PumpAiBase.java b/src/main/java/forge/card/ability/ai/PumpAiBase.java index 61dd8277a24..f03a5a67d58 100644 --- a/src/main/java/forge/card/ability/ai/PumpAiBase.java +++ b/src/main/java/forge/card/ability/ai/PumpAiBase.java @@ -386,6 +386,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { protected boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int defense, final int attack, final List keywords) { final Game game = ai.getGame(); + final Combat combat = game.getCombat(); PhaseHandler phase = game.getPhaseHandler(); if (!c.canBeTargetedBy(sa)) { @@ -437,22 +438,22 @@ public abstract class PumpAiBase extends SpellAbilityAi { // is the creature unblocked and the spell will pump its power? if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) - && game.getCombat().isAttacking(c) && game.getCombat().isUnblocked(c) && attack > 0) { + && combat.isAttacking(c) && combat.isUnblocked(c) && attack > 0) { return true; } // is the creature blocked and the blocker would survive if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && attack > 0 - && game.getCombat().isAttacking(c) - && game.getCombat().isBlocked(c) - && game.getCombat().getBlockers(c) != null - && !game.getCombat().getBlockers(c).isEmpty() - && !ComputerUtilCombat.blockerWouldBeDestroyed(ai, game.getCombat().getBlockers(c).get(0))) { + && combat.isAttacking(c) + && combat.isBlocked(c) + && combat.getBlockers(c) != null + && !combat.getBlockers(c).isEmpty() + && !ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0))) { return true; } // if the life of the computer is in danger, try to pump blockers blocking Tramplers - List blockedBy = game.getCombat().getAttackersBlockedBy(c); + List blockedBy = combat.getAttackersBlockedBy(c); boolean attackerHasTrample = false; for (Card b : blockedBy) { attackerHasTrample |= b.hasKeyword("Trample"); diff --git a/src/main/java/forge/card/ability/ai/PumpAllAi.java b/src/main/java/forge/card/ability/ai/PumpAllAi.java index 9bb9bbe11d5..4e7762f0a93 100644 --- a/src/main/java/forge/card/ability/ai/PumpAllAi.java +++ b/src/main/java/forge/card/ability/ai/PumpAllAi.java @@ -91,7 +91,7 @@ public class PumpAllAi extends PumpAiBase { } totalPower += Math.min(c.getNetAttack(), power * -1); if (phase == PhaseType.COMBAT_DECLARE_BLOCKERS - && game.getCombat().getUnblockedAttackers().contains(c)) { + && game.getCombat().isUnblocked(c)) { if (ComputerUtilCombat.lifeInDanger(sa.getActivatingPlayer(), game.getCombat())) { return true; } diff --git a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java index 8078d8bf447..6ea28f22bf1 100644 --- a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java +++ b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java @@ -507,10 +507,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (sa.hasParam("Ninjutsu") || sa.hasParam("Attacking")) { // What should they attack? + // TODO Ninjutsu needs to actually select the Defender, instead of auto selecting player List defenders = game.getCombat().getDefenders(); if (!defenders.isEmpty()) { - game.getCombat().addAttacker(tgtC, defenders.get(0)); - game.getCombat().addUnblockedAttacker(tgtC); + // Blockeres are already declared, set this to unblocked + game.getCombat().addAttacker(tgtC, defenders.get(0), false); } } if (sa.hasParam("Tapped") || sa.hasParam("Ninjutsu")) { diff --git a/src/main/java/forge/game/ai/AiAttackController.java b/src/main/java/forge/game/ai/AiAttackController.java index 0489b682e3d..c4505f32755 100644 --- a/src/main/java/forge/game/ai/AiAttackController.java +++ b/src/main/java/forge/game/ai/AiAttackController.java @@ -787,7 +787,7 @@ public class AiAttackController { final int blockNum = this.blockers.size(); int attackNum = 0; int damage = 0; - List attacking = combat.getAttackersByDefenderSlot(iDefender); + List attacking = combat.getAttackersOf(defender); CardLists.sortByPowerAsc(attacking); for (Card atta : attacking) { if (attackNum >= blockNum || !CombatUtil.canBeBlocked(attacker, this.blockers)) { diff --git a/src/main/java/forge/game/ai/ComputerUtilCombat.java b/src/main/java/forge/game/ai/ComputerUtilCombat.java index 7569ba39d33..15ae940127b 100644 --- a/src/main/java/forge/game/ai/ComputerUtilCombat.java +++ b/src/main/java/forge/game/ai/ComputerUtilCombat.java @@ -219,7 +219,7 @@ public class ComputerUtilCombat { int damage = 0; - final List attackers = combat.getAttackersByDefenderSlot(0); + final List attackers = combat.getAttackersOf(ai); final List unblocked = new ArrayList(); for (final Card attacker : attackers) { @@ -261,7 +261,7 @@ public class ComputerUtilCombat { int poison = 0; - final List attackers = combat.getAttackersByDefenderSlot(0); + final List attackers = combat.getAttackersOf(ai); final List unblocked = new ArrayList(); for (final Card attacker : attackers) { @@ -305,7 +305,7 @@ public class ComputerUtilCombat { } // check for creatures that must be blocked - final List attackers = combat.getAttackersByDefenderSlot(0); + final List attackers = combat.getAttackersOf(ai); for (final Card attacker : attackers) { @@ -359,7 +359,7 @@ public class ComputerUtilCombat { } // check for creatures that must be blocked - final List attackers = combat.getAttackersByDefenderSlot(0); + final List attackers = combat.getAttackersOf(ai); for (final Card attacker : attackers) { @@ -551,7 +551,8 @@ public class ComputerUtilCombat { */ public static boolean attackerWouldBeDestroyed(Player ai, final Card attacker) { final Game game = ai.getGame(); - final List blockers = game.getCombat().getBlockers(attacker); + final Combat combat = game.getCombat(); + final List blockers = combat.getBlockers(attacker); for (final Card defender : blockers) { if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, game.getCombat(), true) diff --git a/src/main/java/forge/game/combat/AttackingBand.java b/src/main/java/forge/game/combat/AttackingBand.java new file mode 100644 index 00000000000..852ecedc22f --- /dev/null +++ b/src/main/java/forge/game/combat/AttackingBand.java @@ -0,0 +1,91 @@ +package forge.game.combat; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Predicate; + +import forge.Card; +import forge.CardLists; +import forge.GameEntity; +import forge.item.IPaperCard.Predicates; + +/** + * TODO: Write javadoc for this type. + * + */ +public class AttackingBand implements Comparable { + + private List attackers = new ArrayList(); + private List blockers = new ArrayList(); + private GameEntity defender = null; + private Boolean blocked = null; + + public AttackingBand(List band, GameEntity def) { + attackers.addAll(band); + this.defender = def; + } + + public AttackingBand(Card card, GameEntity def) { + attackers.add(card); + this.defender = def; + } + + public List getAttackers() { return this.attackers; } + public List getBlockers() { return this.blockers; } + public GameEntity getDefender() { return this.defender; } + + public void addAttacker(Card card) { attackers.add(card); } + public void removeAttacker(Card card) { attackers.remove(card); } + + public void addBlocker(Card card) { blockers.add(card); } + public void removeBlocker(Card card) { blockers.remove(card); } + public void setBlockers(List blockers) { this.blockers = blockers; } + + public void setDefender(GameEntity def) { this.defender = def; } + + public void setBlocked(boolean blocked) { this.blocked = blocked; } + public boolean getBlocked() { return this.blocked != null && this.blocked.booleanValue(); } + + public void calculateBlockedState() { this.blocked = !this.blockers.isEmpty(); } + + public boolean canJoinBand(Card card) { + // If this card has banding it can definitely join + if (card.hasKeyword("Banding")) { + return true; + } + + // If all of the cards in the Band have banding, it can definitely join + if (attackers.size() == CardLists.getKeyword(attackers, "Banding").size()) { + return true; + } + + // TODO add checks for bands with other + //List bandsWithOther = CardLists.getKeyword(attackers, "Bands with Other"); + + return false; + } + + /* (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(AttackingBand o) { + if (o == null) { + return -1; + } + + List compareAttackers = o.getAttackers(); + + int sizeDiff = this.attackers.size() - compareAttackers.size(); + if (sizeDiff > 0) { + return 1; + } else if (sizeDiff < 0) { + return -1; + } else if (sizeDiff == 0 && this.attackers.isEmpty()) { + return 0; + } + + return this.attackers.get(0).compareTo(compareAttackers.get(0)); + } +} diff --git a/src/main/java/forge/game/phase/Combat.java b/src/main/java/forge/game/phase/Combat.java index 88ae95a77ae..567106fd9ea 100644 --- a/src/main/java/forge/game/phase/Combat.java +++ b/src/main/java/forge/game/phase/Combat.java @@ -19,11 +19,9 @@ package forge.game.phase; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import java.util.TreeMap; import com.google.common.collect.Lists; @@ -33,6 +31,7 @@ import forge.CardLists; import forge.CardPredicates; import forge.GameEntity; import forge.card.trigger.TriggerType; +import forge.game.combat.AttackingBand; import forge.game.event.GameEventBlockerAssigned; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -46,34 +45,23 @@ import forge.game.zone.ZoneType; * @version $Id$ */ public class Combat { - // key is attacker Card - // value is List of blockers - private final Map> attackerMap = new TreeMap>(); - private final Map> blockerMap = new TreeMap>(); + // List of AttackingBands + private final List attackingBands = new ArrayList(); + // Attacker -> AttackingBand (Attackers can only be in a single band) + private final Map attackerToBandMap = new TreeMap(); + // Blocker -> AttackingBands (Blockers can block multiple bands/creatures + private final Map> blockerToBandsMap = new TreeMap>(); - private final Set blocked = new HashSet(); - private final Set unblocked = new HashSet(); private final HashMap defendingDamageMap = new HashMap(); - // Defenders are the Defending Player + Each controlled Planeswalker - private List defenders = new ArrayList(); + // Defenders are all Opposing Players + Planeswalker's Controller By Opposing Players private Map> defenderMap = new HashMap>(); - - - // This Hash keeps track of - private final HashMap attackerToDefender = new HashMap(); + + private Map> blockerDamageAssignmentOrder = new TreeMap>(); + private Map> attackerDamageAssignmentOrder = new TreeMap>(); private Player attackingPlayer = null; - /** - *

- * Constructor for Combat. - *

- */ - public Combat() { - // Let the Begin Turn/Untap Phase Reset Combat properly - } - /** *

* reset. @@ -81,11 +69,7 @@ public class Combat { */ public final void reset(Player playerTurn) { this.resetAttackers(); - this.blocked.clear(); - - this.unblocked.clear(); this.defendingDamageMap.clear(); - this.attackingPlayer = playerTurn; this.initiatePossibleDefenders(playerTurn.getOpponents()); @@ -100,7 +84,6 @@ public class Combat { * a {@link forge.game.player.Player} object. */ public final void initiatePossibleDefenders(final Iterable defenders) { - this.defenders.clear(); this.defenderMap.clear(); for (Player defender : defenders) { fillDefenderMaps(defender); @@ -108,22 +91,19 @@ public class Combat { } public final void initiatePossibleDefenders(final Player defender) { - this.defenders.clear(); this.defenderMap.clear(); fillDefenderMaps(defender); } public final boolean isCombat() { - return !attackerMap.isEmpty(); + return !attackingBands.isEmpty(); } private void fillDefenderMaps(final Player defender) { - this.defenders.add(defender); this.defenderMap.put(defender, new ArrayList()); List planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); for (final Card pw : planeswalkers) { - this.defenders.add(pw); this.defenderMap.put(pw, new ArrayList()); } } @@ -136,7 +116,7 @@ public class Combat { * @return a {@link java.util.ArrayList} object. */ public final List getDefenders() { - return this.defenders; + return new ArrayList(this.defenderMap.keySet()); } /** @@ -148,8 +128,8 @@ public class Combat { * a {@link java.util.ArrayList} object. */ public final void setDefenders(final List newDef) { - this.defenders = newDef; - for (GameEntity entity : this.defenders) { + this.defenderMap.clear(); + for (GameEntity entity : newDef) { this.defenderMap.put(entity, new ArrayList()); } } @@ -164,7 +144,7 @@ public class Combat { public final List getDefendingPlaneswalkers() { final List pwDefending = new ArrayList(); - for (final GameEntity o : this.defenders) { + for (final GameEntity o : this.defenderMap.keySet()) { if (o instanceof Card) { pwDefending.add((Card) o); } @@ -196,18 +176,6 @@ public class Combat { return this.attackingPlayer; } - - /** - *

- * Getter for the field defendingDamageMap. - *

- * - * @return a {@link java.util.HashMap} object. - */ - public final HashMap getDefendingDamageMap() { - return this.defendingDamageMap; - } - /** *

* addDefendingDamage. @@ -235,11 +203,6 @@ public class Combat { } } - public final List getAttackersByDefenderSlot(int slot) { - GameEntity entity = this.defenders.get(slot); - return this.defenderMap.get(entity); - } - public final List getAttackersOf(GameEntity defender) { return defenderMap.get(defender); } @@ -254,28 +217,39 @@ public class Combat { * @return a boolean. */ public final boolean isAttacking(final Card c) { - return this.attackerMap.containsKey(c); + return this.attackerToBandMap.containsKey(c); } - /** - *

- * addAttacker. - *

- * - * @param c - * a {@link forge.Card} object. - * @param defender - * a GameEntity object. - */ public final void addAttacker(final Card c, GameEntity defender) { - if (!defenders.contains(defender)) { + addAttacker(c, defender, null); + } + + public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) { + addAttacker(c, defender, band, null); + } + + public final void addAttacker(final Card c, GameEntity defender, boolean blocked) { + addAttacker(c, defender, null, blocked); + } + + public final void addAttacker(final Card c, GameEntity defender, AttackingBand band, Boolean blocked) { + if (!defenderMap.containsKey(defender)) { System.out.println("Trying to add Attacker " + c + " to missing defender " + defender); return; } - - this.attackerMap.put(c, new ArrayList()); - this.attackerToDefender.put(c, defender); + + if (band == null || !this.attackingBands.contains(band)) { + band = new AttackingBand(c, defender); + if (blocked != null) { + band.setBlocked(blocked.booleanValue()); + } + this.attackingBands.add(band); + } else { + band.addAttacker(c); + } + // Attacker -> Defender and Defender -> Attacker map to Bands? this.defenderMap.get(defender).add(c); + this.attackerToBandMap.put(c, band); } /** @@ -288,7 +262,7 @@ public class Combat { * @return a {@link java.lang.Object} object. */ public final GameEntity getDefenderByAttacker(final Card c) { - return this.attackerToDefender.get(c); + return this.attackerToBandMap.get(c).getDefender(); } public final Player getDefenderPlayerByAttacker(final Card c) { @@ -307,26 +281,30 @@ public class Combat { } public final GameEntity getDefendingEntity(final Card c) { - GameEntity defender = this.attackerToDefender.get(c); - - if (this.defenders.contains(defender)) { + GameEntity defender = this.getDefenderByAttacker(c); + if (this.defenderMap.containsKey(defender)) { return defender; } System.out.println("Attacker " + c + " missing defender " + defender); - return null; } + public final AttackingBand getBandByAttacker(final Card c) { + return this.attackerToBandMap.get(c); + } + /** *

* resetAttackers. *

*/ public final void resetAttackers() { - this.attackerMap.clear(); - this.attackerToDefender.clear(); - this.blockerMap.clear(); + this.attackingBands.clear(); + this.blockerToBandsMap.clear(); + this.attackerToBandMap.clear(); + this.blockerDamageAssignmentOrder.clear(); + this.attackerDamageAssignmentOrder.clear(); } /** @@ -336,28 +314,24 @@ public class Combat { * * @return an array of {@link forge.Card} objects. */ - public final List getAttackers() { - return new ArrayList(this.attackerMap.keySet()); + public final List getAttackingBands() { + return attackingBands; } // getAttackers() + + public final List getAttackers() { + List attackers = new ArrayList(); + for(AttackingBand band : attackingBands) { + attackers.addAll(band.getAttackers()); + } + return attackers; + } - /** - *

- * isBlocked. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @return a boolean. - */ public final boolean isBlocked(final Card attacker) { - return this.blocked.contains(attacker); + return this.attackerToBandMap.get(attacker).getBlocked(); } public final void setBlocked(final Card attacker) { - if (!this.blocked.contains(attacker)) { - this.blocked.add(attacker); - this.unblocked.remove(attacker); - } + this.attackerToBandMap.get(attacker).setBlocked(true); } /** @@ -371,24 +345,23 @@ public class Combat { * a {@link forge.Card} object. */ public final void addBlocker(final Card attacker, final Card blocker) { - this.blocked.add(attacker); - this.attackerMap.get(attacker).add(blocker); - if (!this.blockerMap.containsKey(blocker)) { - this.blockerMap.put(blocker, Lists.newArrayList(attacker)); + AttackingBand band = this.attackerToBandMap.get(attacker); + band.addBlocker(blocker); + + if (!this.blockerToBandsMap.containsKey(blocker)) { + this.blockerToBandsMap.put(blocker, Lists.newArrayList(band)); } else { - this.blockerMap.get(blocker).add(attacker); + this.blockerToBandsMap.get(blocker).add(band); } attacker.getGame().fireEvent(new GameEventBlockerAssigned()); } public final void removeBlockAssignment(final Card attacker, final Card blocker) { - this.attackerMap.get(attacker).remove(blocker); - this.blockerMap.get(blocker).remove(attacker); - if (this.attackerMap.get(attacker).isEmpty()) { - this.blocked.remove(attacker); - } - if (this.blockerMap.get(blocker).isEmpty()) { - this.blockerMap.remove(blocker); + AttackingBand band = this.attackerToBandMap.get(attacker); + band.removeBlocker(blocker); + this.blockerToBandsMap.get(blocker).remove(attacker); + if (this.blockerToBandsMap.get(blocker).isEmpty()) { + this.blockerToBandsMap.remove(blocker); } } @@ -401,17 +374,12 @@ public class Combat { * a {@link forge.Card} object. */ public final void undoBlockingAssignment(final Card blocker) { - final List att = this.getAttackers(); - for (final Card attacker : att) { - if (this.getBlockers(attacker).contains(blocker)) { - this.getBlockingAttackerList(attacker).remove(blocker); - if (this.getBlockers(attacker).isEmpty()) { - this.blocked.remove(attacker); - } - } + final List att = this.blockerToBandsMap.get(blocker); + for (final AttackingBand band : att) { + band.removeBlocker(blocker); } - this.blockerMap.remove(blocker); - } // undoBlockingAssignment(Card) + this.blockerToBandsMap.remove(blocker); + } /** *

@@ -422,25 +390,37 @@ public class Combat { */ public final List getAllBlockers() { final List block = new ArrayList(); - block.addAll(blockerMap.keySet()); + block.addAll(blockerToBandsMap.keySet()); return block; - } // getAllBlockers() + } - /** - *

- * getBlockers. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @return a {@link forge.CardList} object. - */ - public final List getBlockers(final Card attacker) { - if (this.getBlockingAttackerList(attacker) == null) { + public final List getBlockers(final AttackingBand band) { + List list = band.getBlockers(); + if (list == null) { return new ArrayList(); } else { - return new ArrayList(this.getBlockingAttackerList(attacker)); + return new ArrayList(list); + } + } + + public final List getBlockers(final Card card) { + return getBlockers(card, false); + } + + public final List getBlockers(final Card card, boolean ordered) { + // If requesting the ordered blocking lsit pass true, directly. + List list = null; + if (ordered) { + list = this.attackerDamageAssignmentOrder.containsKey(card) ? this.attackerDamageAssignmentOrder.get(card) : null; + } else { + list = this.getBandByAttacker(card).getBlockers(); + } + + if (list == null) { + return new ArrayList(); + } else { + return new ArrayList(list); } } @@ -454,26 +434,16 @@ public class Combat { * @return a {@link forge.Card} object. */ public final List getAttackersBlockedBy(final Card blocker) { - if (blockerMap.containsKey(blocker)) { - return blockerMap.get(blocker); + List blocked = new ArrayList(); + + if (blockerToBandsMap.containsKey(blocker)) { + for(AttackingBand band : blockerToBandsMap.get(blocker)) { + blocked.addAll(band.getAttackers()); + } } - return new ArrayList(); + return blocked; } - /** - *

- * getList. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @return a {@link forge.CardList} object. - */ - private List getBlockingAttackerList(final Card attacker) { - return this.attackerMap.get(attacker); - } - - /** *

* getDefendingPlayer. @@ -499,7 +469,8 @@ public class Combat { return players; } - // return all defenders + // Can't figure out who it's related to... just return all??? + // return all defending players List defenders = this.getDefenders(); for (GameEntity ge : defenders) { if (ge instanceof Player) { @@ -509,56 +480,42 @@ public class Combat { return players; } - /** - *

- * setBlockerList. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param blockers - * a {@link forge.CardList} object. - */ - public void setBlockerList(final Card attacker, final List blockers) { - this.attackerMap.put(attacker, blockers); + public void setAttackerDamageAssignmentOrder(final Card attacker, final List blockers) { + this.attackerDamageAssignmentOrder.put(attacker, blockers); } - public void setAttackersBlockedByList(final Card blocker, final List attackers) { - this.blockerMap.put(blocker, attackers); + public void setBlockerDamageAssignmentOrder(final Card blocker, final List attackers) { + this.blockerDamageAssignmentOrder.put(blocker, attackers); } - /** - *

- * removeFromCombat. - *

- * - * @param c - * a {@link forge.Card} object. - */ public final void removeFromCombat(final Card c) { - // todo(sol) add some more solid error checking in here // is card an attacker? - if (this.attackerMap.containsKey(c)) { - // Keep track of all of the different maps - List blockers = this.attackerMap.get(c); - this.attackerMap.remove(c); + if (this.attackerToBandMap.containsKey(c)) { + // Soooo many maps to keep track of + AttackingBand band = this.attackerToBandMap.get(c); + band.removeAttacker(c); + this.attackerToBandMap.remove(c); + this.attackerDamageAssignmentOrder.remove(c); + + List blockers = band.getBlockers(); for (Card b : blockers) { - this.blockerMap.get(b).remove(c); + if (band.getAttackers().isEmpty()) { + this.blockerToBandsMap.get(b).remove(c); + } + // Clear removed attacker from assignment order + this.blockerDamageAssignmentOrder.get(b).remove(c); } - // Keep track of all of the different maps - GameEntity entity = this.attackerToDefender.get(c); - this.attackerToDefender.remove(c); - this.defenderMap.get(entity).remove(c); - } else if (this.blockerMap.containsKey(c)) { // card is a blocker - List attackers = this.blockerMap.get(c); + this.defenderMap.get(band.getDefender()).remove(c); + } else if (this.blockerDamageAssignmentOrder.containsKey(c)) { // card is a blocker + List attackers = this.blockerToBandsMap.get(c); - boolean stillDeclaring = c.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS); - this.blockerMap.remove(c); - for (Card a : attackers) { - this.attackerMap.get(a).remove(c); - if (stillDeclaring && this.attackerMap.get(a).isEmpty()) { - this.blocked.remove(a); + this.blockerToBandsMap.remove(c); + this.blockerDamageAssignmentOrder.remove(c); + for (AttackingBand a : attackers) { + a.removeBlocker(c); + for(Card atk : a.getAttackers()) { + this.attackerDamageAssignmentOrder.get(atk).remove(c); } } } @@ -571,7 +528,9 @@ public class Combat { */ public final void removeAbsentCombatants() { final List all = new ArrayList(); - all.addAll(this.getAttackers()); + for(AttackingBand band : this.getAttackingBands()) { + all.addAll(band.getAttackers()); + } all.addAll(this.getAllBlockers()); for (int i = 0; i < all.size(); i++) { @@ -587,21 +546,18 @@ public class Combat { *

*/ public final void setUnblockedAttackers() { - final List attacking = this.getAttackers(); - - for (final Card attacker : attacking) { - final List block = this.getBlockers(attacker); - - if (block.isEmpty()) { - // this damage is assigned to a player by setPlayerDamage() - this.addUnblockedAttacker(attacker); - - // Run Unblocked Trigger - final HashMap runParams = new HashMap(); - runParams.put("Attacker", attacker); - runParams.put("Defender",this.getDefenderByAttacker(attacker)); - attacker.getGame().getTriggerHandler().runTrigger(TriggerType.AttackerUnblocked, runParams, false); + final List attacking = this.getAttackingBands(); + for (final AttackingBand band : attacking) { + band.calculateBlockedState(); + if (band.getBlocked()) { + for (Card attacker : band.getAttackers()) { + // Run Unblocked Trigger + final HashMap runParams = new HashMap(); + runParams.put("Attacker", attacker); + runParams.put("Defender",this.getDefenderByAttacker(attacker)); + attacker.getGame().getTriggerHandler().runTrigger(TriggerType.AttackerUnblocked, runParams, false); + } } } } @@ -612,13 +568,25 @@ public class Combat { for (final Card blocker : blockers) { if (blocker.hasDoubleStrike() || blocker.hasFirstStrike() == firstStrikeDamage) { - List attackers = this.getAttackersBlockedBy(blocker); + List attackers = this.blockerDamageAssignmentOrder.get(blocker); final int damage = blocker.getNetCombatDamage(); if (!attackers.isEmpty()) { + Player attackingPlayer = this.getAttackingPlayer(); + Player assigningPlayer = blocker.getController(); + + List bandingAttackers = CardLists.getKeyword(attackers, "Banding"); + if (!bandingAttackers.isEmpty()) { + assigningPlayer = attackingPlayer; + } else { + // TODO Get each bands with other creature + // Check if any other valid creatures matches the bands with other + // assigningPlayer = blockingBand.get(0).getController(); + } + assignedDamage = true; - Map map = blocker.getController().getController().assignCombatDamage(blocker, attackers, damage, null, false); + Map map = blocker.getController().getController().assignCombatDamage(blocker, attackers, damage, null, assigningPlayer != blocker.getController()); for (Entry dt : map.entrySet()) { dt.getKey().addAssignedDamage(dt.getValue(), blocker); dt.getKey().updateObservers(); @@ -646,31 +614,32 @@ public class Combat { if (damageDealt <= 0) { continue; } + + AttackingBand band = this.getBandByAttacker(attacker); boolean trampler = attacker.hasKeyword("Trample"); - blockers = this.getBlockers(attacker); + blockers = this.attackerDamageAssignmentOrder.get(attacker); assignedDamage = true; // If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender if (blockers.isEmpty()) { - if (trampler || this.isUnblocked(attacker)) { + if (trampler || !band.getBlocked()) { this.addDefendingDamage(damageDealt, attacker); - } else { - // Else no damage can be dealt anywhere - continue; - } + } // No damage happens if blocked but no blockers left } else { - GameEntity defender = this.getDefenderByAttacker(attacker); + GameEntity defender = band.getDefender(); Player assigningPlayer = this.getAttackingPlayer(); // Defensive Formation is very similar to Banding with Blockers // 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; - } - - if (!assigningPlayer.equals(defender)) { + } else { List blockingBand = CardLists.getKeyword(blockers, "Banding"); if (!blockingBand.isEmpty()) { assigningPlayer = blockingBand.get(0).getController(); + } else { + // TODO Get each bands with other creature + // Check if any other valid creatures matches the bands with other + // assigningPlayer = blockingBand.get(0).getController(); } } @@ -704,7 +673,7 @@ public class Combat { public void dealAssignedDamage() { // This function handles both Regular and First Strike combat assignment - final HashMap defMap = this.getDefendingDamageMap(); + final HashMap defMap = this.defendingDamageMap; final HashMap> wasDamaged = new HashMap>(); for (final Entry entry : defMap.entrySet()) { @@ -784,7 +753,7 @@ public class Combat { * @return a boolean. */ public final boolean isUnblocked(final Card att) { - return this.unblocked.contains(att); + return !this.attackerToBandMap.get(att).getBlocked(); } /** @@ -795,56 +764,26 @@ public class Combat { * @return an array of {@link forge.Card} objects. */ public final List getUnblockedAttackers() { - final List out = new ArrayList(); - for (Card c : this.unblocked) { - if (!c.hasFirstStrike()) { - out.add(c); + ArrayList unblocked = new ArrayList(); + for (AttackingBand band : this.attackingBands) { + if (!band.getBlocked()) { + unblocked.addAll(band.getAttackers()); } } - return out; - } // getUnblockedAttackers() - /** - *

- * getUnblockedFirstStrikeAttackers. - *

- * - * @return an array of {@link forge.Card} objects. - */ - public final List getUnblockedFirstStrikeAttackers() { - final List out = new ArrayList(); - for (Card c : this.unblocked) { // only add creatures without firstStrike to this - if (c.hasFirstStrike() || c.hasDoubleStrike()) { - out.add(c); - } - } - return out; - } // getUnblockedAttackers() - - /** - *

- * addUnblockedAttacker. - *

- * - * @param c - * a {@link forge.Card} object. - */ - public final void addUnblockedAttacker(final Card c) { - if (!this.unblocked.contains(c)) { - this.unblocked.add(c); - } + return unblocked; } public boolean isPlayerAttacked(Player priority) { - - // System.out.println("\nWho attacks attacks " + priority.toString() + "?"); - for (Card c : getAttackers()) { - - if (priority.equals(getDefenderPlayerByAttacker(c))) { - return true; + for(GameEntity defender : defenderMap.keySet()) { + if ((defender instanceof Player && priority.equals(defender)) || + (defender instanceof Card && priority.equals(((Card)defender).getController()))) { + List attackers = defenderMap.get(priority); + if (attackers != null && !attackers.isEmpty()) + return true; } } + return false; } - } // Class Combat diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index dc0ee47bdf4..1cd08ac6674 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -46,6 +46,7 @@ import forge.card.staticability.StaticAbility; import forge.card.trigger.TriggerType; import forge.game.Game; import forge.game.GlobalRuleChange; +import forge.game.combat.AttackingBand; import forge.game.player.Player; import forge.game.player.PlayerController.ManaPaymentPurpose; import forge.game.zone.ZoneType; @@ -179,11 +180,10 @@ public class CombatUtil { return true; } - if (attacker.hasKeyword("CARDNAME can't be blocked by more than one creature.") - && (combat.getBlockers(attacker).size() > 0)) { + if (attacker.hasKeyword("CARDNAME can't be blocked by more than one creature.") && + !combat.getBlockers(attacker).isEmpty()) { return false; } - return CombatUtil.canBeBlocked(attacker); } @@ -425,33 +425,43 @@ public class CombatUtil { private static void orderMultipleBlockers(final Combat combat) { // If there are multiple blockers, the Attacker declares the Assignment Order final Player player = combat.getAttackingPlayer(); - for (final Card attacker : combat.getAttackers()) { - List blockers = combat.getBlockers(attacker); - if (blockers.size() <= 1) { + for (final AttackingBand band : combat.getAttackingBands()) { + List attackers = band.getAttackers(); + if (attackers.isEmpty()) { continue; } - List orderedBlockers = player.getController().orderBlockers(attacker, blockers); - combat.setBlockerList(attacker, orderedBlockers); - } - // Refresh Combat Panel + 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); + } + } } private 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) { - continue; + orderedAttacker = attackers; + } else { + // Damage Ordering needs to take cards like Melee into account, is that happening? + orderedAttacker = blocker.getController().getController().orderAttackers(blocker, attackers); } - List orderedAttacker = blocker.getController().getController().orderAttackers(blocker, attackers); - combat.setAttackersBlockedByList(blocker, orderedAttacker); + combat.setBlockerDamageAssignmentOrder(blocker, orderedAttacker); } - // Refresh Combat Panel } - // can the blocker block an attacker with a lure effect? /** *

@@ -465,7 +475,6 @@ public class CombatUtil { * @return a boolean. */ public static boolean mustBlockAnAttacker(final Card blocker, final Combat combat) { - if (blocker == null || combat == null) { return false; } @@ -1108,7 +1117,7 @@ public class CombatUtil { * a {@link forge.Card} object. */ public static void checkBlockedAttackers(final Game game, final Card a, final List blockers) { - + final Combat combat = game.getCombat(); if (blockers.isEmpty()) { return; } @@ -1135,7 +1144,7 @@ public class CombatUtil { if (m.find()) { final String[] k = keyword.split(" "); final int magnitude = Integer.valueOf(k[1]); - final int numBlockers = game.getCombat().getBlockers(a).size(); + final int numBlockers = combat.getBlockers(a).size(); if (numBlockers > 1) { CombatUtil.executeRampageAbility(game, a, magnitude, numBlockers); } @@ -1253,60 +1262,6 @@ public class CombatUtil { } } - /* - public static void souverignsOfAlara2ndAbility(final Game game, final Card attacker) { - final Ability ability4 = new Ability(attacker, ManaCost.ZERO) { - @Override - public void resolve() { - List enchantments = - CardLists.filter(attacker.getController().getCardsIn(ZoneType.Library), new Predicate() { - @Override - public boolean apply(final Card c) { - if (attacker.hasKeyword("Protection from enchantments") - || (attacker.hasKeyword("Protection from everything"))) { - return false; - } - return (c.isEnchantment() && c.hasKeyword("Enchant creature") && !CardFactoryUtil - .hasProtectionFrom(c, attacker)); - } - }); - final Player player = attacker.getController(); - Card enchantment = null; - if (player.isHuman()) { - final Card[] target = new Card[enchantments.size()]; - for (int j = 0; j < enchantments.size(); j++) { - final Card crd = enchantments.get(j); - target[j] = crd; - } - final Object check = GuiChoose.oneOrNone( - "Select enchantment to enchant exalted creature", target); - if (check != null) { - enchantment = ((Card) check); - } - } else { - enchantment = ComputerUtilCard.getBestEnchantmentAI(enchantments, this, false); - } - if ((enchantment != null) && attacker.isInPlay()) { - game.getAction().changeZone(game.getZoneOf(enchantment), - enchantment.getOwner().getZone(ZoneType.Battlefield), enchantment, null); - enchantment.enchantEntity(attacker); - } - attacker.getController().shuffle(); - } // resolve - }; // ability4 - - final StringBuilder sb4 = new StringBuilder(); - sb4.append(attacker).append( - " - (Exalted) searches library for an Aura card that could enchant that creature, "); - sb4.append("put it onto the battlefield attached to that creature, then shuffles library."); - ability4.setDescription(sb4.toString()); - ability4.setStackDescription(sb4.toString()); - - game.getStack().addSimultaneousStackEntry(ability4); - } - */ - - /** * executes Rampage abilities for a given card. * @param game diff --git a/src/main/java/forge/gui/match/controllers/CCombat.java b/src/main/java/forge/gui/match/controllers/CCombat.java index 5bc0879ee07..34ec796b0cf 100644 --- a/src/main/java/forge/gui/match/controllers/CCombat.java +++ b/src/main/java/forge/gui/match/controllers/CCombat.java @@ -91,7 +91,7 @@ public enum CCombat implements ICDoc { display.append(" > "); display.append(combatantToString(c)).append("\n"); - List blockers = combat.getBlockers(c); + List blockers = combat.getBlockers(c, true); // loop through blockers for (final Card element : blockers) {