diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index 1c0b181e819..a3c6e9f9e80 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -892,6 +892,12 @@ public class ComputerPlayer> extends PlayerImpl i return min; } + @Override + public UUID chooseAttackerOrder(List attackers, Game game) { + //TODO: improve this + return attackers.iterator().next().getId(); + } + @Override public UUID chooseBlockerOrder(List blockers, Game game) { //TODO: improve this diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 561a05fdda4..d5b5ccad711 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -469,6 +469,22 @@ public class HumanPlayer extends PlayerImpl { } } + @Override + public UUID chooseAttackerOrder(List attackers, Game game) { + while (!abort) { + game.fireSelectTargetEvent(playerId, "Pick attacker", attackers, true); + waitForResponse(); + if (response.getUUID() != null) { + for (Permanent perm: attackers) { + if (perm.getId().equals(response.getUUID())) + return perm.getId(); + } + } + } + return null; + } + + @Override public UUID chooseBlockerOrder(List blockers, Game game) { while (!abort) { diff --git a/Mage.Server/plugins/mage-player-ai-ma.jar b/Mage.Server/plugins/mage-player-ai-ma.jar index fb7ec1859bd..1e83097c7fc 100644 Binary files a/Mage.Server/plugins/mage-player-ai-ma.jar and b/Mage.Server/plugins/mage-player-ai-ma.jar differ diff --git a/Mage.Server/plugins/mage-player-ai.jar b/Mage.Server/plugins/mage-player-ai.jar index 3872e13ae24..25e06869861 100644 Binary files a/Mage.Server/plugins/mage-player-ai.jar and b/Mage.Server/plugins/mage-player-ai.jar differ diff --git a/Mage.Server/plugins/mage-player-aiminimax.jar b/Mage.Server/plugins/mage-player-aiminimax.jar index 337b6d855d7..bc9fe15ec9a 100644 Binary files a/Mage.Server/plugins/mage-player-aiminimax.jar and b/Mage.Server/plugins/mage-player-aiminimax.jar differ diff --git a/Mage.Server/plugins/mage-player-human.jar b/Mage.Server/plugins/mage-player-human.jar index 27e0ec6212e..215a15622fa 100644 Binary files a/Mage.Server/plugins/mage-player-human.jar and b/Mage.Server/plugins/mage-player-human.jar differ diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index 9c986f50b4e..98a20f72c02 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -28,12 +28,6 @@ package mage.game.combat; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; import mage.Constants.Outcome; import mage.abilities.effects.RequirementEffect; import mage.abilities.keyword.VigilanceAbility; @@ -48,9 +42,11 @@ import mage.players.PlayerList; import mage.target.common.TargetDefender; import mage.util.Copyable; +import java.io.Serializable; +import java.util.*; + /** - * * @author BetaSteward_at_googlemail.com */ public class Combat implements Serializable, Copyable { @@ -60,32 +56,41 @@ public class Combat implements Serializable, Copyable { private static FilterCreatureForCombat filterBlockers = new FilterCreatureForCombat(); protected List groups = new ArrayList(); + protected Map blockingGroups = new HashMap(); protected Set defenders = new HashSet(); protected UUID attackerId; //the player that is attacking - public Combat() {} + public Combat() { + } public Combat(final Combat combat) { this.attackerId = combat.attackerId; - for (CombatGroup group: combat.groups) { + for (CombatGroup group : combat.groups) { groups.add(group.copy()); } - for (UUID defenderId: combat.defenders) { + for (UUID defenderId : combat.defenders) { defenders.add(defenderId); } + for (Map.Entry group : combat.blockingGroups.entrySet()) { + blockingGroups.put(group.getKey(), group.getValue()); + } } public List getGroups() { return groups; } + public Collection getBlockingGroups() { + return blockingGroups.values(); + } + public Set getDefenders() { return defenders; } public List getAttackers() { List attackers = new ArrayList(); - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { attackers.addAll(group.attackers); } return attackers; @@ -93,7 +98,7 @@ public class Combat implements Serializable, Copyable { public List getBlockers() { List blockers = new ArrayList(); - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { blockers.addAll(group.blockers); } return blockers; @@ -101,6 +106,7 @@ public class Combat implements Serializable, Copyable { public void clear() { groups.clear(); + blockingGroups.clear(); defenders.clear(); attackerId = null; } @@ -108,7 +114,7 @@ public class Combat implements Serializable, Copyable { public int getValue(Game game) { StringBuilder sb = new StringBuilder(); sb.append(attackerId).append(defenders); - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { sb.append(group.getValue(game)); } return sb.toString().hashCode(); @@ -131,23 +137,21 @@ public class Combat implements Serializable, Copyable { protected void checkAttackRequirements(Player player, Game game) { //20101001 - 508.1d - for (Permanent creature: game.getBattlefield().getAllActivePermanents(filterAttackers, player.getId())) { - for (RequirementEffect effect: game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) { + for (Permanent creature : game.getBattlefield().getAllActivePermanents(filterAttackers, player.getId())) { + for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) { if (effect.mustAttack(game)) { UUID defenderId = effect.mustAttackDefender(game.getContinuousEffects().getAbility(effect.getId()), game); if (defenderId == null) { if (defenders.size() == 1) { player.declareAttacker(creature.getId(), defenders.iterator().next(), game); - } - else { + } else { TargetDefender target = new TargetDefender(defenders, creature.getId()); target.setRequired(true); if (player.chooseTarget(Outcome.Damage, target, null, game)) { player.declareAttacker(creature.getId(), target.getFirstTarget(), game); } } - } - else { + } else { player.declareAttacker(creature.getId(), defenderId, game); } } @@ -160,7 +164,7 @@ public class Combat implements Serializable, Copyable { Player player = game.getPlayer(attackerId); //20101001 - 509.1c checkBlockRequirements(player, game); - for (UUID defenderId: getPlayerDefenders(game)) { + for (UUID defenderId : getPlayerDefenders(game)) { game.getPlayer(defenderId).selectBlockers(game); game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); } @@ -170,9 +174,9 @@ public class Combat implements Serializable, Copyable { protected void checkBlockRequirements(Player player, Game game) { //20101001 - 509.1c //TODO: handle case where more than one attacker must be blocked - for (Permanent creature: game.getBattlefield().getActivePermanents(filterBlockers, player.getId(), game)) { + for (Permanent creature : game.getBattlefield().getActivePermanents(filterBlockers, player.getId(), game)) { if (game.getOpponents(attackerId).contains(creature.getControllerId())) { - for (RequirementEffect effect: game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) { + for (RequirementEffect effect : game.getContinuousEffects().getApplicableRequirementEffects(creature, game)) { if (effect.mustBlock(game)) { UUID attackId = effect.mustBlockAttacker(game.getContinuousEffects().getAbility(effect.getId()), game); Player defender = game.getPlayer(creature.getControllerId()); @@ -210,7 +214,7 @@ public class Combat implements Serializable, Copyable { } break; case MULITPLE: - for (UUID opponentId: game.getOpponents(attackerId)) { + for (UUID opponentId : game.getOpponents(attackerId)) { addDefender(opponentId, game); } break; @@ -219,7 +223,7 @@ public class Combat implements Serializable, Copyable { private void addDefender(UUID defenderId, Game game) { defenders.add(defenderId); - for (Permanent permanent: game.getBattlefield().getAllActivePermanents(filterPlaneswalker, defenderId)) { + for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filterPlaneswalker, defenderId)) { defenders.add(permanent.getId()); } } @@ -238,12 +242,36 @@ public class Combat implements Serializable, Copyable { groups.add(newGroup); } + // add blocking group for creatures that block more than one creature + public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game) { + Permanent blocker = game.getPermanent(blockerId); + if (blockerId != null && blocker != null && blocker.getBlocking() > 1) { + if (!blockingGroups.containsKey(blockerId)) { + CombatGroup newGroup = new CombatGroup(playerId, playerId != null); + newGroup.blockers.add(blockerId); + // add all blocked attackers + for (CombatGroup group : groups) { + if (group.getBlockers().contains(blockerId)) { + // take into account banding + for (UUID attacker : group.attackers) { + newGroup.attackers.add(attacker); + } + } + } + blockingGroups.put(blockerId, newGroup); + } else { + //TODO: handle banding + blockingGroups.get(blockerId).attackers.add(attackerId); + } + } + } + public void removeFromCombat(UUID creatureId, Game game) { Permanent creature = game.getPermanent(creatureId); if (creature != null) { creature.setAttacking(false); creature.setBlocking(0); - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { group.remove(creatureId); } } @@ -251,15 +279,15 @@ public class Combat implements Serializable, Copyable { public void endCombat(Game game) { Permanent creature; - for (CombatGroup group: groups) { - for (UUID attacker: group.attackers) { + for (CombatGroup group : groups) { + for (UUID attacker : group.attackers) { creature = game.getPermanent(attacker); if (creature != null) { creature.setAttacking(false); creature.setBlocking(0); } } - for (UUID blocker: group.blockers) { + for (UUID blocker : group.blockers) { creature = game.getPermanent(blocker); if (creature != null) { creature.setAttacking(false); @@ -271,7 +299,7 @@ public class Combat implements Serializable, Copyable { } public boolean hasFirstOrDoubleStrike(Game game) { - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { if (group.hasFirstOrDoubleStrike(game)) return true; } @@ -279,7 +307,7 @@ public class Combat implements Serializable, Copyable { } public CombatGroup findGroup(UUID attackerId) { - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { if (group.getAttackers().contains(attackerId)) return group; } @@ -288,7 +316,7 @@ public class Combat implements Serializable, Copyable { public int totalUnblockedDamage(Game game) { int total = 0; - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { if (group.getBlockers().isEmpty()) { total += group.totalAttackerDamage(game); } @@ -307,7 +335,7 @@ public class Combat implements Serializable, Copyable { } public boolean isAttacked(UUID defenderId, Game game) { - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { if (group.getDefenderId().equals(defenderId)) return true; if (group.defenderIsPlaneswalker) { @@ -321,7 +349,7 @@ public class Combat implements Serializable, Copyable { public UUID getDefendingPlayer(UUID attackerId) { UUID defenderId = null; - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { if (group.getAttackers().contains(attackerId)) { defenderId = group.getDefenderId(); break; @@ -332,13 +360,12 @@ public class Combat implements Serializable, Copyable { private Set getPlayerDefenders(Game game) { Set playerDefenders = new HashSet(); - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { if (group.defenderIsPlaneswalker) { Permanent permanent = game.getPermanent(group.getDefenderId()); if (permanent != null) playerDefenders.add(permanent.getControllerId()); - } - else { + } else { playerDefenders.add(group.getDefenderId()); } } @@ -346,9 +373,15 @@ public class Combat implements Serializable, Copyable { } public void damageAssignmentOrder(Game game) { - for (CombatGroup group: groups) { + for (CombatGroup group : groups) { group.pickBlockerOrder(attackerId, game); } + for (Map.Entry blockingGroup : blockingGroups.entrySet()) { + Permanent blocker = game.getPermanent(blockingGroup.getKey()); + if (blocker != null) { + blockingGroup.getValue().pickAttackerOrder(blocker.getControllerId(), game); + } + } } @Override diff --git a/Mage/src/mage/game/combat/CombatGroup.java b/Mage/src/mage/game/combat/CombatGroup.java index 178d08cd295..4ae91f6b6b1 100644 --- a/Mage/src/mage/game/combat/CombatGroup.java +++ b/Mage/src/mage/game/combat/CombatGroup.java @@ -52,6 +52,7 @@ public class CombatGroup implements Serializable, Copyable { protected List attackers = new ArrayList(); protected List blockers = new ArrayList(); protected List blockerOrder = new ArrayList(); + protected List attackerOrder = new ArrayList(); protected boolean blocked; protected UUID defenderId; protected boolean defenderIsPlaneswalker; @@ -74,6 +75,9 @@ public class CombatGroup implements Serializable, Copyable { for (UUID orderId: group.blockerOrder) { this.blockerOrder.add(orderId); } + for (UUID orderId: group.attackerOrder) { + this.attackerOrder.add(orderId); + } } protected String getValue(Game game) { @@ -148,6 +152,17 @@ public class CombatGroup implements Serializable, Copyable { } } + public void assignDamageToAttackers(boolean first, Game game) { + if (blockers.size() > 0 && (!first || hasFirstOrDoubleStrike(game))) { + if (attackers.size() == 1) { + singleAttackerDamage(first, game); + } + else { + multiAttackerDamage(first, game); + } + } + } + private boolean canDamage(Permanent perm, boolean first) { return (first && hasFirstOrDoubleStrike(perm)) || (!first && !hasFirstStrike(perm)); } @@ -168,7 +183,6 @@ public class CombatGroup implements Serializable, Copyable { Permanent blocker = game.getPermanent(blockers.get(0)); Permanent attacker = game.getPermanent(attackers.get(0)); if (blocker != null && attacker != null) { - int blockerDamage = blocker.getPower().getValue(); if (canDamage(attacker, first)) { int damage = attacker.getPower().getValue(); if (hasTrample(attacker)) { @@ -190,7 +204,10 @@ public class CombatGroup implements Serializable, Copyable { } } if (canDamage(blocker, first)) { - attacker.damage(blockerDamage, blocker.getId(), game, true, true); + if (blocker.getBlocking() == 1) { // blocking several creatures handled separately + int blockerDamage = blocker.getPower().getValue(); + attacker.damage(blockerDamage, blocker.getId(), game, true, true); + } } } } @@ -198,10 +215,11 @@ public class CombatGroup implements Serializable, Copyable { private void multiBlockerDamage(boolean first, Game game) { //TODO: handle banding Permanent attacker = game.getPermanent(attackers.get(0)); + if (attacker == null) { + return; + } Player player = game.getPlayer(attacker.getControllerId()); int damage = attacker.getPower().getValue(); - if (attacker == null) - return; if (canDamage(attacker, first)) { Map assigned = new HashMap(); for (UUID blockerId: blockerOrder) { @@ -230,7 +248,9 @@ public class CombatGroup implements Serializable, Copyable { for (UUID blockerId: blockerOrder) { Permanent blocker = game.getPermanent(blockerId); if (canDamage(blocker, first)) { - attacker.damage(blocker.getPower().getValue(), blocker.getId(), game, true, true); + if (blocker.getBlocking() == 1) { // blocking several creatures handled separately + attacker.damage(blocker.getPower().getValue(), blocker.getId(), game, true, true); + } } } // Issue#73 @@ -249,6 +269,69 @@ public class CombatGroup implements Serializable, Copyable { } } + /** + * Damages attacking creatures by a creature that blocked several ones + * Damages only attackers as blocker was damage in {@link #singleBlockerDamage}. + * + * Handles abilities like "{this} an block any number of creatures.". + * + * @param first + * @param game + */ + private void singleAttackerDamage(boolean first, Game game) { + Permanent blocker = game.getPermanent(blockers.get(0)); + Permanent attacker = game.getPermanent(attackers.get(0)); + if (blocker != null && attacker != null) { + if (canDamage(blocker, first)) { + int damage = blocker.getPower().getValue(); + attacker.damage(damage, blocker.getId(), game, true, true); + } + } + } + + /** + * Damages attacking creatures by a creature that blocked several ones + * Damages only attackers as blocker was damage in either {@link #singleBlockerDamage} or {@link #multiBlockerDamage}. + * + * Handles abilities like "{this} an block any number of creatures.". + * + * @param first + * @param game + */ + private void multiAttackerDamage(boolean first, Game game) { + Permanent blocker = game.getPermanent(blockers.get(0)); + Player player = game.getPlayer(blocker.getControllerId()); + if (blocker == null) { + return; + } + int damage = blocker.getPower().getValue(); + + if (canDamage(blocker, first)) { + Map assigned = new HashMap(); + for (UUID attackerId: attackerOrder) { + Permanent attacker = game.getPermanent(attackerId); + int lethalDamage; + if (blocker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) + lethalDamage = 1; + else + lethalDamage = attacker.getToughness().getValue() - attacker.getDamage(); + if (lethalDamage >= damage) { + assigned.put(attackerId, damage); + damage = 0; + break; + } + int damageAssigned = player.getAmount(lethalDamage, damage, "Assign damage to " + attacker.getName(), game); + assigned.put(attackerId, damageAssigned); + damage -= damageAssigned; + } + + for (Map.Entry entry : assigned.entrySet()) { + Permanent attacker = game.getPermanent(entry.getKey()); + attacker.damage(entry.getValue(), blocker.getId(), game, true, true); + } + } + } + private void defenderDamage(Permanent attacker, int amount, Game game) { if (this.defenderIsPlaneswalker) { Permanent defender = game.getPermanent(defenderId); @@ -277,7 +360,7 @@ public class CombatGroup implements Serializable, Copyable { } } Permanent blocker = game.getPermanent(blockerId); - if (blockerId != null) { + if (blockerId != null && blocker != null) { blocker.setBlocking(blocker.getBlocking() + 1); blockers.add(blockerId); blockerOrder.add(blockerId); @@ -310,6 +393,29 @@ public class CombatGroup implements Serializable, Copyable { } } } + + public void pickAttackerOrder(UUID playerId, Game game) { + if (attackers.isEmpty()) + return; + Player player = game.getPlayer(playerId); + List attackerList = new ArrayList(attackers); + attackerOrder.clear(); + while (true) { + if (attackerList.size() == 1) { + attackerOrder.add(attackerList.get(0)); + break; + } + else { + List attackerPerms = new ArrayList(); + for (UUID attackerId: attackerList) { + attackerPerms.add(game.getPermanent(attackerId)); + } + UUID attackerId = player.chooseAttackerOrder(attackerPerms, game); + attackerOrder.add(attackerId); + attackerList.remove(attackerId); + } + } + } public int totalAttackerDamage(Game game) { int total = 0; diff --git a/Mage/src/mage/game/turn/CombatDamageStep.java b/Mage/src/mage/game/turn/CombatDamageStep.java index fea9274efb6..7a72be55968 100644 --- a/Mage/src/mage/game/turn/CombatDamageStep.java +++ b/Mage/src/mage/game/turn/CombatDamageStep.java @@ -70,6 +70,9 @@ public class CombatDamageStep extends Step { for (CombatGroup group: game.getCombat().getGroups()) { group.assignDamage(first, game); } + for (CombatGroup group : game.getCombat().getBlockingGroups()) { + group.assignDamageToAttackers(first, game); + } } public boolean getFirst() { diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 491d660ee36..5fef0679a5c 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -165,6 +165,7 @@ public interface Player extends MageItem, Copyable { public abstract Mode chooseMode(Modes modes, Ability source, Game game); public abstract void selectAttackers(Game game); public abstract void selectBlockers(Game game); + public abstract UUID chooseAttackerOrder(List attacker, Game game); public abstract UUID chooseBlockerOrder(List blockers, Game game); public abstract void assignDamage(int damage, List targets, String singleTargetName, UUID sourceId, Game game); public abstract int getAmount(int min, int max, String message, Game game); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 11e0ad2146b..ae22dfff243 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -849,6 +849,7 @@ public abstract class PlayerImpl> implements Player, Ser CombatGroup group = game.getCombat().findGroup(attackerId); if (blocker != null && group != null && group.canBlock(blocker, game)) { group.addBlocker(blockerId, playerId, game); + game.getCombat().addBlockingGroup(blockerId, attackerId, playerId, game); } }