forked from External/mage
910 lines
39 KiB
Java
910 lines
39 KiB
Java
/*
|
||
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
|
||
*
|
||
* Redistribution and use in source and binary forms, with or without modification, are
|
||
* permitted provided that the following conditions are met:
|
||
*
|
||
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||
* conditions and the following disclaimer.
|
||
*
|
||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||
* of conditions and the following disclaimer in the documentation and/or other materials
|
||
* provided with the distribution.
|
||
*
|
||
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
|
||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
*
|
||
* The views and conclusions contained in the software and documentation are those of the
|
||
* authors and should not be interpreted as representing official policies, either expressed
|
||
* or implied, of BetaSteward_at_googlemail.com.
|
||
*/
|
||
package mage.game.combat;
|
||
|
||
import java.io.Serializable;
|
||
import java.util.*;
|
||
|
||
import mage.abilities.common.ControllerAssignCombatDamageToBlockersAbility;
|
||
import mage.abilities.common.ControllerDivideCombatDamageAbility;
|
||
import mage.abilities.common.DamageAsThoughNotBlockedAbility;
|
||
import mage.abilities.keyword.BandingAbility;
|
||
import mage.abilities.keyword.CantBlockAloneAbility;
|
||
import mage.abilities.keyword.DeathtouchAbility;
|
||
import mage.abilities.keyword.DoubleStrikeAbility;
|
||
import mage.abilities.keyword.FirstStrikeAbility;
|
||
import mage.abilities.keyword.TrampleAbility;
|
||
import mage.constants.Outcome;
|
||
import mage.filter.StaticFilters;
|
||
import mage.game.Game;
|
||
import mage.game.events.GameEvent;
|
||
import mage.game.permanent.Permanent;
|
||
import mage.players.Player;
|
||
import mage.util.Copyable;
|
||
|
||
/**
|
||
*
|
||
* @author BetaSteward_at_googlemail.com
|
||
*/
|
||
public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
||
|
||
protected List<UUID> attackers = new ArrayList<>();
|
||
protected List<UUID> blockers = new ArrayList<>();
|
||
protected List<UUID> blockerOrder = new ArrayList<>();
|
||
protected List<UUID> attackerOrder = new ArrayList<>();
|
||
protected Map<UUID, UUID> players = new HashMap<>();
|
||
protected boolean blocked;
|
||
protected UUID defenderId; // planeswalker or player
|
||
protected UUID defendingPlayerId;
|
||
protected boolean defenderIsPlaneswalker;
|
||
|
||
/**
|
||
* @param defenderId the player that controls the defending permanents
|
||
* @param defenderIsPlaneswalker is the defending permanent a planeswalker
|
||
* @param defendingPlayerId regular controller of the defending permanents
|
||
*/
|
||
public CombatGroup(UUID defenderId, boolean defenderIsPlaneswalker, UUID defendingPlayerId) {
|
||
this.defenderId = defenderId;
|
||
this.defenderIsPlaneswalker = defenderIsPlaneswalker;
|
||
this.defendingPlayerId = defendingPlayerId;
|
||
}
|
||
|
||
public CombatGroup(final CombatGroup group) {
|
||
this.attackers.addAll(group.attackers);
|
||
this.blockers.addAll(group.blockers);
|
||
this.blockerOrder.addAll(group.blockerOrder);
|
||
this.attackerOrder.addAll(group.attackerOrder);
|
||
this.players.putAll(group.players);
|
||
this.blocked = group.blocked;
|
||
this.defenderId = group.defenderId;
|
||
this.defendingPlayerId = group.defendingPlayerId;
|
||
this.defenderIsPlaneswalker = group.defenderIsPlaneswalker;
|
||
}
|
||
|
||
public boolean hasFirstOrDoubleStrike(Game game) {
|
||
for (UUID permId : attackers) {
|
||
Permanent attacker = game.getPermanent(permId);
|
||
if (attacker != null && hasFirstOrDoubleStrike(attacker)) {
|
||
return true;
|
||
}
|
||
}
|
||
for (UUID permId : blockers) {
|
||
Permanent blocker = game.getPermanent(permId);
|
||
if (blocker != null && hasFirstOrDoubleStrike(blocker)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public UUID getDefenderId() {
|
||
return defenderId;
|
||
}
|
||
|
||
public UUID getDefendingPlayerId() {
|
||
return defendingPlayerId;
|
||
}
|
||
|
||
public List<UUID> getAttackers() {
|
||
return attackers;
|
||
}
|
||
|
||
public List<UUID> getBlockers() {
|
||
return blockers;
|
||
}
|
||
|
||
public List<UUID> getBlockerOrder() {
|
||
return blockerOrder;
|
||
}
|
||
|
||
private boolean hasFirstOrDoubleStrike(Permanent perm) {
|
||
return perm.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId()) || perm.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId());
|
||
}
|
||
|
||
private boolean hasFirstStrike(Permanent perm) {
|
||
return perm.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId());
|
||
}
|
||
|
||
private boolean hasDoubleStrike(Permanent perm) {
|
||
return perm.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId());
|
||
}
|
||
|
||
private boolean hasTrample(Permanent perm) {
|
||
return perm.getAbilities().containsKey(TrampleAbility.getInstance().getId());
|
||
}
|
||
|
||
private boolean hasBanding(Permanent perm) {
|
||
return perm.getAbilities().containsKey(BandingAbility.getInstance().getId());
|
||
}
|
||
|
||
public void assignDamageToBlockers(boolean first, Game game) {
|
||
if (!attackers.isEmpty() && (!first || hasFirstOrDoubleStrike(game))) {
|
||
Permanent attacker = game.getPermanent(attackers.get(0));
|
||
if (attacker != null && !assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(attacker, attacker.getControllerId(), first, game, true)) {
|
||
if (blockers.isEmpty()) {
|
||
unblockedDamage(first, game);
|
||
return;
|
||
} else {
|
||
Player player = game.getPlayer(defenderAssignsCombatDamage(game) ? defendingPlayerId : attacker.getControllerId());
|
||
if (attacker.getAbilities().containsKey(DamageAsThoughNotBlockedAbility.getInstance().getId())) { // for handling creatures like Thorn Elemental
|
||
if (player.chooseUse(Outcome.Damage, "Do you wish to assign damage for " + attacker.getLogName() + " as though it weren't blocked?", null, game)) {
|
||
blocked = false;
|
||
unblockedDamage(first, game);
|
||
}
|
||
}
|
||
if (blockers.size() == 1) {
|
||
singleBlockerDamage(player, first, game);
|
||
} else {
|
||
multiBlockerDamage(player, first, game);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public void assignDamageToAttackers(boolean first, Game game) {
|
||
if (!blockers.isEmpty() && (!first || hasFirstOrDoubleStrike(game))) {
|
||
// this should only come up if Butcher Orgg is granted the ability to block multiple blockers
|
||
for (UUID blockerId : blockers) {
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(blocker, blocker.getControllerId(), first, game, false)) {
|
||
return;
|
||
}
|
||
}
|
||
if (attackers.size() != 1) {
|
||
multiAttackerDamage(first, game);
|
||
// } else {
|
||
// singleAttackerDamage(first, game);
|
||
}
|
||
}
|
||
}
|
||
|
||
public void applyDamage(Game game) {
|
||
for (UUID uuid : attackers) {
|
||
Permanent permanent = game.getPermanent(uuid);
|
||
if (permanent != null) {
|
||
permanent.applyDamage(game);
|
||
}
|
||
}
|
||
for (UUID uuid : blockers) {
|
||
Permanent permanent = game.getPermanent(uuid);
|
||
if (permanent != null) {
|
||
permanent.applyDamage(game);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Determines if permanent can damage in current (First Strike or not)
|
||
* combat damage step
|
||
*
|
||
* @param perm Permanent to check
|
||
* @param first First strike or common combat damage step
|
||
* @return
|
||
*/
|
||
private boolean canDamage(Permanent perm, boolean first) {
|
||
if (perm == null) {
|
||
return false;
|
||
}
|
||
// if now first strike combat damage step
|
||
if (first) {
|
||
// should have first strike or double strike
|
||
return hasFirstOrDoubleStrike(perm);
|
||
} // if now not first strike combat
|
||
else {
|
||
if (hasFirstStrike(perm)) {
|
||
// if it has first strike in non FS combat damage step
|
||
// then it can damage only if it has ALSO double strike
|
||
// Fixes Issue 200
|
||
return hasDoubleStrike(perm);
|
||
}
|
||
// can damage otherwise
|
||
return true;
|
||
}
|
||
}
|
||
|
||
private void unblockedDamage(boolean first, Game game) {
|
||
for (UUID attackerId : attackers) {
|
||
Permanent attacker = game.getPermanent(attackerId);
|
||
if (canDamage(attacker, first)) {
|
||
//20091005 - 510.1c, 702.17c
|
||
if (!blocked || hasTrample(attacker)) {
|
||
defenderDamage(attacker, getDamageValueFromPermanent(attacker, game), game);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void singleBlockerDamage(Player player, boolean first, Game game) {
|
||
Permanent blocker = game.getPermanent(blockers.get(0));
|
||
Permanent attacker = game.getPermanent(attackers.get(0));
|
||
if (blocker != null && attacker != null) {
|
||
int blockerDamage = getDamageValueFromPermanent(blocker, game); // must be set before attacker damage marking because of effects like Test of Faith
|
||
if (blocked && canDamage(attacker, first)) {
|
||
int damage = getDamageValueFromPermanent(attacker, game);
|
||
if (hasTrample(attacker)) {
|
||
int lethalDamage;
|
||
if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) {
|
||
lethalDamage = 1;
|
||
} else {
|
||
lethalDamage = Math.max(blocker.getToughness().getValue() - blocker.getDamage(), 0);
|
||
}
|
||
if (lethalDamage >= damage) {
|
||
blocker.markDamage(damage, attacker.getId(), game, true, true);
|
||
} else {
|
||
int damageAssigned = player.getAmount(lethalDamage, damage, "Assign damage to " + blocker.getName(), game);
|
||
blocker.markDamage(damageAssigned, attacker.getId(), game, true, true);
|
||
damage -= damageAssigned;
|
||
if (damage > 0) {
|
||
defenderDamage(attacker, damage, game);
|
||
}
|
||
}
|
||
} else {
|
||
blocker.markDamage(damage, attacker.getId(), game, true, true);
|
||
}
|
||
}
|
||
if (canDamage(blocker, first)) {
|
||
if (checkSoleBlockerAfter(blocker, game)) { // blocking several creatures handled separately
|
||
if (!assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(blocker, blocker.getControllerId(), first, game, false)) {
|
||
attacker.markDamage(blockerDamage, blocker.getId(), game, true, true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void multiBlockerDamage(Player player, boolean first, Game game) {
|
||
Permanent attacker = game.getPermanent(attackers.get(0));
|
||
if (attacker == null) {
|
||
return;
|
||
}
|
||
boolean oldRuleDamage = (Objects.equals(player.getId(), defendingPlayerId));
|
||
int damage = getDamageValueFromPermanent(attacker, game);
|
||
if (canDamage(attacker, first)) {
|
||
// must be set before attacker damage marking because of effects like Test of Faith
|
||
Map<UUID, Integer> blockerPower = new HashMap<>();
|
||
for (UUID blockerId : blockerOrder) {
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (canDamage(blocker, first)) {
|
||
if (checkSoleBlockerAfter(blocker, game)) { // blocking several creatures handled separately
|
||
blockerPower.put(blockerId, getDamageValueFromPermanent(blocker, game));
|
||
}
|
||
}
|
||
}
|
||
Map<UUID, Integer> assigned = new HashMap<>();
|
||
if (blocked) {
|
||
boolean excessDamageToDefender = true;
|
||
for (UUID blockerId : new ArrayList<>(blockerOrder)) { // prevent ConcurrentModificationException
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (blocker != null) {
|
||
int lethalDamage;
|
||
if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) {
|
||
lethalDamage = 1;
|
||
} else {
|
||
lethalDamage = Math.max(blocker.getToughness().getValue() - blocker.getDamage(), 0);
|
||
}
|
||
if (lethalDamage >= damage) {
|
||
if (!oldRuleDamage) {
|
||
assigned.put(blockerId, damage);
|
||
damage = 0;
|
||
break;
|
||
} else if (damage == 0) {
|
||
break;
|
||
}
|
||
}
|
||
int damageAssigned = 0;
|
||
if (!oldRuleDamage) {
|
||
damageAssigned = player.getAmount(lethalDamage, damage, "Assign damage to " + blocker.getName(), game);
|
||
} else {
|
||
damageAssigned = player.getAmount(0, damage, "Assign damage to " + blocker.getName(), game);
|
||
if (damageAssigned < lethalDamage) {
|
||
excessDamageToDefender = false; // all blockers need to have lethal damage assigned before it can trample over to the defender
|
||
}
|
||
}
|
||
assigned.put(blockerId, damageAssigned);
|
||
damage -= damageAssigned;
|
||
}
|
||
}
|
||
if (damage > 0 && hasTrample(attacker) && excessDamageToDefender) {
|
||
defenderDamage(attacker, damage, game);
|
||
} else if (!blockerOrder.isEmpty()) {
|
||
// Assign the damage left to first blocker
|
||
assigned.put(blockerOrder.get(0), assigned.get(blockerOrder.get(0)) + damage);
|
||
}
|
||
}
|
||
for (UUID blockerId : blockerOrder) {
|
||
Integer power = blockerPower.get(blockerId);
|
||
if (power != null) {
|
||
// might be missing canDamage condition?
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (!assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(blocker, blocker.getControllerId(), first, game, false)) {
|
||
attacker.markDamage(power, blockerId, game, true, true);
|
||
}
|
||
}
|
||
}
|
||
for (Map.Entry<UUID, Integer> entry : assigned.entrySet()) {
|
||
Permanent blocker = game.getPermanent(entry.getKey());
|
||
blocker.markDamage(entry.getValue(), attacker.getId(), game, true, true);
|
||
}
|
||
} else {
|
||
for (UUID blockerId : blockerOrder) {
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (canDamage(blocker, first)) {
|
||
if (!assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(blocker, blocker.getControllerId(), first, game, false)) {
|
||
attacker.markDamage(getDamageValueFromPermanent(blocker, game), blocker.getId(), game, true, true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private void defendingPlayerAndOrDefendingCreaturesDividedDamage(Permanent attacker, Player player, boolean first, Game game, boolean isAttacking) {
|
||
// for handling Butcher Orgg
|
||
if (!((blocked && blockers.isEmpty() && isAttacking) || (attackers.isEmpty() && !isAttacking))) {
|
||
if (attacker == null) {
|
||
return;
|
||
}
|
||
int damage = getDamageValueFromPermanent(attacker, game);
|
||
if (canDamage(attacker, first)) {
|
||
// must be set before attacker damage marking because of effects like Test of Faith
|
||
Map<UUID, Integer> blockerPower = new HashMap<>();
|
||
for (UUID blockerId : blockerOrder) {
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (canDamage(blocker, first)) {
|
||
if (checkSoleBlockerAfter(blocker, game)) { // blocking several creatures handled separately
|
||
blockerPower.put(blockerId, getDamageValueFromPermanent(blocker, game));
|
||
}
|
||
}
|
||
}
|
||
Map<UUID, Integer> assigned = new HashMap<>();
|
||
for (Permanent defendingCreature : game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_PERMANENT_CREATURE, defendingPlayerId, game)) {
|
||
if (defendingCreature != null) {
|
||
if (!(damage > 0)) {
|
||
break;
|
||
}
|
||
int damageAssigned = 0;
|
||
damageAssigned = player.getAmount(0, damage, "Assign damage to " + defendingCreature.getName(), game);
|
||
assigned.put(defendingCreature.getId(), damageAssigned);
|
||
damage -= damageAssigned;
|
||
}
|
||
}
|
||
if (damage > 0) {
|
||
Player defendingPlayer = game.getPlayer(defendingPlayerId);
|
||
if (defendingPlayer.isInGame()) {
|
||
defendingPlayer.damage(damage, attacker.getId(), game, true, true);
|
||
}
|
||
}
|
||
if (isAttacking) {
|
||
for (UUID blockerId : blockerOrder) {
|
||
Integer power = blockerPower.get(blockerId);
|
||
if (power != null) {
|
||
// might be missing canDamage condition?
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (!assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(blocker, blocker.getControllerId(), first, game, false)) {
|
||
attacker.markDamage(power, blockerId, game, true, true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
for (Map.Entry<UUID, Integer> entry : assigned.entrySet()) {
|
||
Permanent defendingCreature = game.getPermanent(entry.getKey());
|
||
defendingCreature.markDamage(entry.getValue(), attacker.getId(), game, true, true);
|
||
}
|
||
} else {
|
||
if (isAttacking) {
|
||
for (UUID blockerId : blockerOrder) {
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (canDamage(blocker, first)) {
|
||
if (!assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(blocker, blocker.getControllerId(), first, game, false)) {
|
||
attacker.markDamage(getDamageValueFromPermanent(blocker, game), blocker.getId(), game, true, true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public boolean checkSoleBlockerAfter (Permanent blocker, Game game) {
|
||
// this solves some corner cases (involving banding) when finding out whether a blocker is blocking alone or not
|
||
if (blocker.getBlocking() == 1) {
|
||
if (game.getCombat().blockingGroups.get(blocker.getId()) == null) {
|
||
return true;
|
||
} else {
|
||
for (CombatGroup group : game.getCombat().getBlockingGroups()) {
|
||
if (group.blockers.contains(blocker.getId())) {
|
||
if (group.attackers.size() == 1) {
|
||
return true; // if blocker is blocking a band, this won't be true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 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.".
|
||
*
|
||
* Blocker damage for blockers blocking single creatures is handled in
|
||
* the single/multi blocker methods, so this shouldn't be used anymore.
|
||
*
|
||
* @param first
|
||
* @param game
|
||
* @deprecated
|
||
*/
|
||
@Deprecated
|
||
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 = getDamageValueFromPermanent(blocker, game);
|
||
attacker.markDamage(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));
|
||
if (blocker == null) {
|
||
return;
|
||
}
|
||
boolean oldRuleDamage = attackerAssignsCombatDamage(game); // handles banding
|
||
Player player = game.getPlayer(oldRuleDamage ? game.getCombat().getAttackingPlayerId() : blocker.getControllerId());
|
||
int damage = getDamageValueFromPermanent(blocker, game);
|
||
|
||
if (canDamage(blocker, first)) {
|
||
Map<UUID, Integer> assigned = new HashMap<>();
|
||
for (UUID attackerId : attackerOrder) {
|
||
Permanent attacker = game.getPermanent(attackerId);
|
||
if (attacker != null) {
|
||
int lethalDamage;
|
||
if (blocker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) {
|
||
lethalDamage = 1;
|
||
} else {
|
||
lethalDamage = Math.max(attacker.getToughness().getValue() - attacker.getDamage(), 0);
|
||
}
|
||
if (lethalDamage >= damage) {
|
||
if (!oldRuleDamage) {
|
||
assigned.put(attackerId, damage);
|
||
damage = 0;
|
||
break;
|
||
} else if (damage == 0) {
|
||
break;
|
||
}
|
||
}
|
||
int damageAssigned = 0;
|
||
if (!oldRuleDamage) {
|
||
damageAssigned = player.getAmount(lethalDamage, damage, "Assign damage to " + attacker.getName(), game);
|
||
} else {
|
||
damageAssigned = player.getAmount(0, damage, "Assign damage to " + attacker.getName(), game);
|
||
}
|
||
assigned.put(attackerId, damageAssigned);
|
||
damage -= damageAssigned;
|
||
}
|
||
}
|
||
if (damage > 0) {
|
||
// Assign the damage left to first attacker
|
||
assigned.put(attackerOrder.get(0), assigned.get(attackerOrder.get(0)) + damage);
|
||
}
|
||
|
||
for (Map.Entry<UUID, Integer> entry : assigned.entrySet()) {
|
||
Permanent attacker = game.getPermanent(entry.getKey());
|
||
attacker.markDamage(entry.getValue(), blocker.getId(), game, true, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void defenderDamage(Permanent attacker, int amount, Game game) {
|
||
if (this.defenderIsPlaneswalker) {
|
||
Permanent defender = game.getPermanent(defenderId);
|
||
if (defender != null) {
|
||
defender.markDamage(amount, attacker.getId(), game, true, true);
|
||
}
|
||
} else {
|
||
Player defender = game.getPlayer(defenderId);
|
||
if (defender.isInGame()) {
|
||
defender.damage(amount, attacker.getId(), game, true, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
public boolean canBlock(Permanent blocker, Game game) {
|
||
// player can't block if another player is attacked
|
||
if (!defendingPlayerId.equals(blocker.getControllerId())) {
|
||
return false;
|
||
}
|
||
for (UUID attackerId : attackers) {
|
||
if (!blocker.canBlock(attackerId, game)) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
*
|
||
* @param blockerId
|
||
* @param playerId controller of the blocking creature
|
||
* @param game
|
||
*/
|
||
public void addBlocker(UUID blockerId, UUID playerId, Game game) {
|
||
for (UUID attackerId : attackers) {
|
||
if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKER, attackerId, blockerId, playerId))) {
|
||
return;
|
||
}
|
||
}
|
||
addBlockerToGroup(blockerId, playerId, game);
|
||
}
|
||
|
||
/**
|
||
* Adds a blocker to a combat group without creating a DECLARE_BLOCKER
|
||
* event.
|
||
*
|
||
* @param blockerId
|
||
* @param playerId controller of the blocking creature
|
||
* @param game
|
||
*/
|
||
public void addBlockerToGroup(UUID blockerId, UUID playerId, Game game) {
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (blockerId != null && blocker != null) {
|
||
blocker.setBlocking(blocker.getBlocking() + 1);
|
||
blockers.add(blockerId);
|
||
blockerOrder.add(blockerId);
|
||
this.blocked = true;
|
||
this.players.put(blockerId, playerId);
|
||
}
|
||
}
|
||
|
||
public void pickBlockerOrder(UUID playerId, Game game) {
|
||
if (blockers.isEmpty()) {
|
||
return;
|
||
}
|
||
Player player = game.getPlayer(playerId); // game.getPlayer(defenderAssignsCombatDamage(game) ? defendingPlayerId : playerId); // this was incorrect because defenderAssignsCombatDamage might be false by the time damage is dealt
|
||
List<UUID> blockerList = new ArrayList<>(blockers);
|
||
blockerOrder.clear();
|
||
while (player.canRespond()) {
|
||
if (blockerList.size() == 1) {
|
||
blockerOrder.add(blockerList.get(0));
|
||
break;
|
||
} else {
|
||
List<Permanent> blockerPerms = new ArrayList<>();
|
||
for (UUID blockerId : blockerList) {
|
||
blockerPerms.add(game.getPermanent(blockerId));
|
||
}
|
||
UUID blockerId = player.chooseBlockerOrder(blockerPerms, this, blockerOrder, game);
|
||
blockerOrder.add(blockerId);
|
||
blockerList.remove(blockerId);
|
||
}
|
||
}
|
||
}
|
||
|
||
public void pickAttackerOrder(UUID playerId, Game game) {
|
||
if (attackers.isEmpty()) {
|
||
return;
|
||
}
|
||
Player player = game.getPlayer(playerId);
|
||
List<UUID> attackerList = new ArrayList<>(attackers);
|
||
attackerOrder.clear();
|
||
while (true) {
|
||
if (attackerList.size() == 1) {
|
||
attackerOrder.add(attackerList.get(0));
|
||
break;
|
||
} else {
|
||
List<Permanent> attackerPerms = new ArrayList<>();
|
||
for (UUID attackerId : attackerList) {
|
||
attackerPerms.add(game.getPermanent(attackerId));
|
||
}
|
||
UUID attackerId = player.chooseAttackerOrder(attackerPerms, game);
|
||
if (!player.isInGame()) {
|
||
break;
|
||
}
|
||
attackerOrder.add(attackerId);
|
||
attackerList.remove(attackerId);
|
||
}
|
||
}
|
||
}
|
||
|
||
public int totalAttackerDamage(Game game) {
|
||
int total = 0;
|
||
for (UUID attackerId : attackers) {
|
||
total += getDamageValueFromPermanent(game.getPermanent(attackerId), game);
|
||
}
|
||
return total;
|
||
}
|
||
|
||
public boolean isDefenderIsPlaneswalker() {
|
||
return defenderIsPlaneswalker;
|
||
}
|
||
|
||
public boolean removeAttackedPlaneswalker(UUID planeswalkerId) {
|
||
if (defenderIsPlaneswalker && defenderId.equals(planeswalkerId)) {
|
||
defenderId = null;
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public boolean remove(UUID creatureId) {
|
||
boolean result = false;
|
||
if (attackers.contains(creatureId)) {
|
||
attackers.remove(creatureId);
|
||
result = true;
|
||
if (attackerOrder.contains(creatureId)) {
|
||
attackerOrder.remove(creatureId);
|
||
}
|
||
} else if (blockers.contains(creatureId)) {
|
||
blockers.remove(creatureId);
|
||
result = true;
|
||
//20100423 - 509.2a
|
||
if (blockerOrder.contains(creatureId)) {
|
||
blockerOrder.remove(creatureId);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
public void acceptBlockers(Game game) {
|
||
if (attackers.isEmpty()) {
|
||
return;
|
||
}
|
||
for (UUID blockerId : blockers) {
|
||
for (UUID attackerId : attackers) {
|
||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.BLOCKER_DECLARED, attackerId, blockerId, players.get(blockerId)));
|
||
}
|
||
}
|
||
|
||
if (!blockers.isEmpty()) {
|
||
for (UUID attackerId : attackers) {
|
||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CREATURE_BLOCKED, attackerId, null));
|
||
}
|
||
}
|
||
}
|
||
|
||
public boolean checkBlockRestrictions(Game game, int blockersCount) {
|
||
boolean blockWasLegal = true;
|
||
if (attackers.isEmpty()) {
|
||
return blockWasLegal;
|
||
}
|
||
if (blockersCount == 1) {
|
||
List<UUID> toBeRemoved = new ArrayList<>();
|
||
for (UUID blockerId : getBlockers()) {
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (blocker != null && blocker.getAbilities().containsKey(CantBlockAloneAbility.getInstance().getId())) {
|
||
blockWasLegal = false;
|
||
if (!game.isSimulation()) {
|
||
game.informPlayers(blocker.getLogName() + " can't block alone. Removing it from combat.");
|
||
}
|
||
toBeRemoved.add(blockerId);
|
||
}
|
||
}
|
||
|
||
for (UUID blockerId : toBeRemoved) {
|
||
game.getCombat().removeBlocker(blockerId, game);
|
||
}
|
||
if (blockers.isEmpty()) {
|
||
this.blocked = false;
|
||
}
|
||
}
|
||
|
||
for (UUID uuid : attackers) {
|
||
Permanent attacker = game.getPermanent(uuid);
|
||
if (attacker != null && this.blocked) {
|
||
// Check if there are enough blockers to have a legal block
|
||
if (attacker.getMinBlockedBy() > 1 && !blockers.isEmpty() && blockers.size() < attacker.getMinBlockedBy()) {
|
||
for (UUID blockerId : new ArrayList<>(blockers)) {
|
||
game.getCombat().removeBlocker(blockerId, game);
|
||
}
|
||
blockers.clear();
|
||
blockerOrder.clear();
|
||
if (!game.isSimulation()) {
|
||
game.informPlayers(attacker.getLogName() + " can't be blocked except by " + attacker.getMinBlockedBy() + " or more creatures. Blockers discarded.");
|
||
}
|
||
blockWasLegal = false;
|
||
}
|
||
// Check if there are too many blockers (maxBlockedBy = 0 means no restrictions)
|
||
if (attacker.getMaxBlockedBy() > 0 && attacker.getMaxBlockedBy() < blockers.size()) {
|
||
for (UUID blockerId : new ArrayList<>(blockers)) {
|
||
game.getCombat().removeBlocker(blockerId, game);
|
||
}
|
||
blockers.clear();
|
||
blockerOrder.clear();
|
||
if (!game.isSimulation()) {
|
||
game.informPlayers(new StringBuilder(attacker.getLogName())
|
||
.append(" can't be blocked by more than ").append(attacker.getMaxBlockedBy())
|
||
.append(attacker.getMaxBlockedBy() == 1 ? " creature." : " creatures.")
|
||
.append(" Blockers discarded.").toString());
|
||
}
|
||
blockWasLegal = false;
|
||
}
|
||
}
|
||
}
|
||
return blockWasLegal;
|
||
}
|
||
|
||
/**
|
||
* There are effects that let creatures assigns combat damage equal to its
|
||
* toughness rather than its power. So this method takes this into account
|
||
* to get the value of damage a creature will assign
|
||
*
|
||
* @param permanent
|
||
* @param game
|
||
* @return
|
||
*/
|
||
private int getDamageValueFromPermanent(Permanent permanent, Game game) {
|
||
if (game.getCombat().useToughnessForDamage(permanent, game)) {
|
||
return permanent.getToughness().getValue();
|
||
} else {
|
||
return permanent.getPower().getValue();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* There are effects, that set an attacker to be blocked. Therefore this
|
||
* setter can be used.
|
||
*
|
||
* This method lacks a band check, use setBlocked(blocked, game) instead.
|
||
*
|
||
* @param blocked
|
||
* @deprecated
|
||
*/
|
||
@Deprecated
|
||
public void setBlocked(boolean blocked) {
|
||
this.blocked = blocked;
|
||
}
|
||
|
||
public void setBlocked(boolean blocked, Game game) {
|
||
this.blocked = blocked;
|
||
for (UUID attackerId : attackers) {
|
||
Permanent attacker = game.getPermanent(attackerId);
|
||
if (attacker != null) {
|
||
for (UUID bandedId : attacker.getBandedCards()) {
|
||
if (!bandedId.equals(attackerId)) {
|
||
CombatGroup bandedGroup = game.getCombat().findGroup(bandedId);
|
||
if (bandedGroup != null) {
|
||
bandedGroup.blocked = blocked;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public boolean getBlocked() {
|
||
return blocked;
|
||
}
|
||
|
||
@Override
|
||
public CombatGroup copy() {
|
||
return new CombatGroup(this);
|
||
}
|
||
|
||
public boolean changeDefenderPostDeclaration(UUID newDefenderId, Game game) {
|
||
if (!defenderId.equals(newDefenderId)) {
|
||
for (UUID attackerId : attackers) { // changing defender will remove a banded attacker from its current band
|
||
Permanent attacker = game.getPermanent(attackerId);
|
||
if (attacker != null && attacker.getBandedCards() != null) {
|
||
for (UUID bandedId : attacker.getBandedCards()) {
|
||
Permanent banded = game.getPermanent(bandedId);
|
||
if (banded != null) {
|
||
banded.removeBandedCard(attackerId);
|
||
}
|
||
}
|
||
}
|
||
attacker.clearBandedCards();
|
||
}
|
||
Permanent permanent = game.getPermanent(newDefenderId);
|
||
if (permanent != null) {
|
||
defenderId = newDefenderId;
|
||
defendingPlayerId = permanent.getControllerId();
|
||
defenderIsPlaneswalker = true;
|
||
return true;
|
||
} else {
|
||
Player defender = game.getPlayer(newDefenderId);
|
||
if (defender != null) {
|
||
defenderId = newDefenderId;
|
||
defendingPlayerId = newDefenderId;
|
||
defenderIsPlaneswalker = false;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Decides damage distribution for attacking banding creatures.
|
||
*
|
||
* @param game
|
||
*/
|
||
public boolean attackerAssignsCombatDamage(Game game) {
|
||
for (UUID attackerId : attackers) {
|
||
Permanent attacker = game.getPermanent(attackerId);
|
||
if (attacker != null) {
|
||
if (hasBanding(attacker)) { // 702.21k - only one attacker with banding necessary
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Decides damage distribution for blocking creatures with banding or
|
||
* if defending player controls the Defensive Formation enchantment.
|
||
*
|
||
* @param game
|
||
*/
|
||
public boolean defenderAssignsCombatDamage(Game game) {
|
||
for (UUID blockerId : blockers) {
|
||
Permanent blocker = game.getPermanent(blockerId);
|
||
if (blocker != null) {
|
||
if (hasBanding(blocker)) { // 702.21j - only one blocker with banding necessary
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
for (Permanent defensiveFormation : game.getBattlefield().getAllActivePermanents(defendingPlayerId)) {
|
||
if (defensiveFormation.getAbilities().containsKey(ControllerAssignCombatDamageToBlockersAbility.getInstance().getId())) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public boolean assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(Permanent creature, UUID playerId, boolean first, Game game, boolean isAttacking) {
|
||
// for handling Butcher Orgg
|
||
if (creature.getAbilities().containsKey(ControllerDivideCombatDamageAbility.getInstance().getId())) {
|
||
Player player = game.getPlayer(defenderAssignsCombatDamage(game) ? defendingPlayerId : (!isAttacking && attackerAssignsCombatDamage(game) ? game.getCombat().getAttackingPlayerId() : playerId));
|
||
// 10/4/2004 If it is blocked but then all of its blockers are removed before combat damage is assigned, then it won’t be able to deal combat damage and you won’t be able to use its ability.
|
||
// (same principle should apply if it's blocking and its blocked attacker is removed from combat)
|
||
if (!((blocked && blockers.isEmpty() && isAttacking) || (attackers.isEmpty() && !isAttacking)) && canDamage(creature, first)) {
|
||
if (player.chooseUse(Outcome.Damage, "Do you wish to assign " + creature.getLogName() + "'s combat damage divided among defending player and/or any number of defending creatures?", null, game)) {
|
||
defendingPlayerAndOrDefendingCreaturesDividedDamage(creature, player, first, game, isAttacking);
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
}
|