forked from External/mage
[refactor/bugfix] use rule 802.2a where appropriate. (#13179)
* [refactor/bugfix] use rule 802.2a where appropriate. Many effects which relied on getDefendingPlayerId would fail if the attacking creature had been removed from combat before they resolved, in which case the defending player ID would be null. This fixes these issues. * Add test for removing attacking creature with Defending Player triggered ability. Change allowFormer to be true by default, reduce falses to only necessary cases.
This commit is contained in:
parent
8de9fb03a4
commit
6b9532febd
8 changed files with 73 additions and 7 deletions
|
|
@ -39,6 +39,7 @@ import org.apache.log4j.Logger;
|
|||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
|
@ -59,6 +60,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
private final List<FilterCreaturePermanent> useToughnessForDamageFilters = new ArrayList<>();
|
||||
|
||||
protected List<CombatGroup> groups = new ArrayList<>();
|
||||
protected List<CombatGroup> formerGroups = new ArrayList<>();
|
||||
protected Map<UUID, CombatGroup> blockingGroups = new HashMap<>();
|
||||
// all possible defenders (players, planeswalkers or battle)
|
||||
protected Set<UUID> defenders = new HashSet<>();
|
||||
|
|
@ -83,6 +85,9 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
for (CombatGroup group : combat.groups) {
|
||||
groups.add(group.copy());
|
||||
}
|
||||
for (CombatGroup group : combat.formerGroups) {
|
||||
formerGroups.add(group.copy());
|
||||
}
|
||||
defenders.addAll(combat.defenders);
|
||||
for (Map.Entry<UUID, CombatGroup> group : combat.blockingGroups.entrySet()) {
|
||||
blockingGroups.put(group.getKey(), group.getValue());
|
||||
|
|
@ -181,6 +186,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
|
||||
public void clear() {
|
||||
groups.clear();
|
||||
formerGroups.clear();
|
||||
blockingGroups.clear();
|
||||
defenders.clear();
|
||||
attackingPlayerId = null;
|
||||
|
|
@ -1679,6 +1685,36 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
* @return
|
||||
*/
|
||||
public UUID getDefendingPlayerId(UUID attackingCreatureId, Game game) {
|
||||
return getDefendingPlayerId(attackingCreatureId, game, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the playerId of the player that is attacked by given attacking
|
||||
* creature or formerly-attacking creature.
|
||||
*
|
||||
* @param attackingCreatureId
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
public UUID getDefendingPlayerId(UUID attackingCreatureId, Game game, boolean allowFormer) {
|
||||
if (allowFormer) {
|
||||
/*
|
||||
* 802.2a. Any rule, object, or effect that refers to a "defending player" refers to one specific defending
|
||||
* player, not to all of the defending players. If an ability of an attacking creature refers to a
|
||||
* defending player, or a spell or ability refers to both an attacking creature and a defending player,
|
||||
* then unless otherwise specified, the defending player it's referring to is the player that creature is
|
||||
* attacking, the controller of the planeswalker that creature is attacking, or the protector of the battle
|
||||
* that player is attacking. If that creature is no longer attacking, the defending player it's referring
|
||||
* to is the player that creature was attacking before it was removed from combat, the controller of the
|
||||
* planeswalker that creature was attacking before it was removed from combat, or the protector of the
|
||||
* battle that player was attacking before it was removed from combat.
|
||||
*/
|
||||
return Stream.concat(groups.stream(), formerGroups.stream())
|
||||
.filter(group -> (group.getAttackers().contains(attackingCreatureId) || group.getFormerAttackers().contains(attackingCreatureId)))
|
||||
.map(CombatGroup::getDefendingPlayerId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
return groups
|
||||
.stream()
|
||||
.filter(group -> group.getAttackers().contains(attackingCreatureId))
|
||||
|
|
@ -1743,6 +1779,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
}
|
||||
}
|
||||
if (group.attackers.isEmpty()) {
|
||||
formerGroups.add(group);
|
||||
groups.remove(group);
|
||||
}
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import java.util.stream.Stream;
|
|||
public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
||||
|
||||
protected List<UUID> attackers = new ArrayList<>();
|
||||
protected List<UUID> formerAttackers = new ArrayList<>();
|
||||
protected List<UUID> blockers = new ArrayList<>();
|
||||
protected List<UUID> blockerOrder = new ArrayList<>();
|
||||
protected List<UUID> attackerOrder = new ArrayList<>();
|
||||
|
|
@ -49,6 +50,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
|
||||
protected CombatGroup(final CombatGroup group) {
|
||||
this.attackers.addAll(group.attackers);
|
||||
this.formerAttackers.addAll(group.formerAttackers);
|
||||
this.blockers.addAll(group.blockers);
|
||||
this.blockerOrder.addAll(group.blockerOrder);
|
||||
this.attackerOrder.addAll(group.attackerOrder);
|
||||
|
|
@ -81,6 +83,10 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
return attackers;
|
||||
}
|
||||
|
||||
public List<UUID> getFormerAttackers() {
|
||||
return formerAttackers;
|
||||
}
|
||||
|
||||
public List<UUID> getBlockers() {
|
||||
return blockers;
|
||||
}
|
||||
|
|
@ -737,6 +743,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
public boolean remove(UUID creatureId) {
|
||||
boolean result = false;
|
||||
if (attackers.contains(creatureId)) {
|
||||
formerAttackers.add(creatureId);
|
||||
attackers.remove(creatureId);
|
||||
result = true;
|
||||
attackerOrder.remove(creatureId);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue