forked from External/mage
Implemented banding (#41)
This commit is contained in:
parent
adec5cf88b
commit
e7301e2c08
24 changed files with 708 additions and 136 deletions
|
|
@ -36,6 +36,7 @@ import java.util.UUID;
|
|||
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;
|
||||
|
|
@ -140,15 +141,19 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
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 (!assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(attacker, attacker.getControllerId(), first, game, true)) {
|
||||
if (attacker != null && !assignsDefendingPlayerAndOrDefendingCreaturesDividedDamage(attacker, attacker.getControllerId(), first, game, true)) {
|
||||
if (blockers.isEmpty()) {
|
||||
unblockedDamage(first, game);
|
||||
return;
|
||||
} else {
|
||||
Player player = game.getPlayer(defenderControlsDefensiveFormation(game) ? defendingPlayerId : attacker.getControllerId());
|
||||
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;
|
||||
|
|
@ -244,7 +249,6 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
|
||||
private void singleBlockerDamage(Player player, boolean first, Game game) {
|
||||
//TODO: handle banding
|
||||
Permanent blocker = game.getPermanent(blockers.get(0));
|
||||
Permanent attacker = game.getPermanent(attackers.get(0));
|
||||
if (blocker != null && attacker != null) {
|
||||
|
|
@ -273,7 +277,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
}
|
||||
if (canDamage(blocker, first)) {
|
||||
if (blocker.getBlocking() == 1) { // blocking several creatures handled separately
|
||||
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);
|
||||
}
|
||||
|
|
@ -283,7 +287,6 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
|
||||
private void multiBlockerDamage(Player player, boolean first, Game game) {
|
||||
//TODO: handle banding
|
||||
Permanent attacker = game.getPermanent(attackers.get(0));
|
||||
if (attacker == null) {
|
||||
return;
|
||||
|
|
@ -296,7 +299,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
for (UUID blockerId : blockerOrder) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (canDamage(blocker, first)) {
|
||||
if (blocker.getBlocking() == 1) { // blocking several creatures handled separately
|
||||
if (checkSoleBlockerAfter(blocker, game)) { // blocking several creatures handled separately
|
||||
blockerPower.put(blockerId, getDamageValueFromPermanent(blocker, game));
|
||||
}
|
||||
}
|
||||
|
|
@ -304,7 +307,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
Map<UUID, Integer> assigned = new HashMap<>();
|
||||
if (blocked) {
|
||||
boolean excessDamageToDefender = true;
|
||||
for (UUID blockerId : new ArrayList<>(blockerOrder)) { // prevent ConcurrentModificationException
|
||||
for (UUID blockerId : blockerOrder) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (blocker != null) {
|
||||
int lethalDamage;
|
||||
|
|
@ -381,7 +384,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
for (UUID blockerId : blockerOrder) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (canDamage(blocker, first)) {
|
||||
if (blocker.getBlocking() == 1) { // blocking several creatures handled separately
|
||||
if (checkSoleBlockerAfter(blocker, game)) { // blocking several creatures handled separately
|
||||
blockerPower.put(blockerId, getDamageValueFromPermanent(blocker, game));
|
||||
}
|
||||
}
|
||||
|
|
@ -435,6 +438,24 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -471,7 +492,8 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
if (blocker == null) {
|
||||
return;
|
||||
}
|
||||
Player player = game.getPlayer(blocker.getControllerId());
|
||||
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)) {
|
||||
|
|
@ -486,11 +508,20 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
lethalDamage = Math.max(attacker.getToughness().getValue() - attacker.getDamage(), 0);
|
||||
}
|
||||
if (lethalDamage >= damage) {
|
||||
assigned.put(attackerId, damage);
|
||||
damage = 0;
|
||||
break;
|
||||
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);
|
||||
}
|
||||
int damageAssigned = player.getAmount(lethalDamage, damage, "Assign damage to " + attacker.getName(), game);
|
||||
assigned.put(attackerId, damageAssigned);
|
||||
damage -= damageAssigned;
|
||||
}
|
||||
|
|
@ -572,7 +603,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
if (blockers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Player player = game.getPlayer(defenderControlsDefensiveFormation(game) ? defendingPlayerId : playerId);
|
||||
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()) {
|
||||
|
|
@ -717,7 +748,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
blockWasLegal = false;
|
||||
}
|
||||
// Check if there are to many blockers (maxBlockedBy = 0 means no restrictions)
|
||||
// Check if there are too many blockers (maxBlockedBy = 0 means no restrictions)
|
||||
if (attacker != null && this.blocked && attacker.getMaxBlockedBy() > 0 && attacker.getMaxBlockedBy() < blockers.size()) {
|
||||
for (UUID blockerId : blockers) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
|
|
@ -759,15 +790,36 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
|
||||
/**
|
||||
* There are effects, that set an attacker to be blcoked. Therefore this
|
||||
* 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;
|
||||
}
|
||||
|
|
@ -778,26 +830,70 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
|
||||
public boolean changeDefenderPostDeclaration(UUID newDefenderId, Game game) {
|
||||
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) {
|
||||
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 = newDefenderId;
|
||||
defenderIsPlaneswalker = false;
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean defenderControlsDefensiveFormation(Game game) {
|
||||
// for handling Defensive Formation
|
||||
/**
|
||||
* 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;
|
||||
|
|
@ -809,7 +905,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
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(defenderControlsDefensiveFormation(game) ? defendingPlayerId : playerId);
|
||||
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)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue