Removed redundant combat code for handling block effect of Odric Master Tactician.

This commit is contained in:
LevelX2 2014-01-28 15:11:23 +01:00
parent 19679c9f6e
commit bd77e476ee
2 changed files with 134 additions and 135 deletions

View file

@ -142,28 +142,12 @@ class OdricMasterTacticianEffect extends ReplacementEffectImpl<OdricMasterTactic
@Override @Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) { public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Player player = game.getPlayer(source.getControllerId()); Player blockController = game.getPlayer(source.getControllerId());
//20101001 - 509.1c if (blockController != null) {
game.getCombat().checkBlockRequirementsBefore(player, game); game.getCombat().selectBlockers(blockController, game);
for (UUID defenderId : game.getCombat().getPlayerDefenders(game)) {
boolean choose = true;
while (choose) {
game.getPlayer(source.getControllerId()).selectBlockers(game, defenderId);
if (game.isPaused() || game.isGameOver()) {
return true; return true;
} }
if (!game.getCombat().checkBlockRestrictions(game.getPlayer(defenderId), game)) { return false;
// only human player can decide to do the block in another way
if (player.isHuman()) {
continue;
}
}
choose = !game.getCombat().checkBlockRequirementsAfter(game.getPlayer(defenderId), player, game);
}
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId));
}
return true;
} }
@Override @Override

View file

@ -1,31 +1,30 @@
/* /*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without modification, are * Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met: * permitted provided that the following conditions are met:
* *
* 1. Redistributions of source code must retain the above copyright notice, this list of * 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. * conditions and the following disclaimer.
* *
* 2. Redistributions in binary form must reproduce the above copyright notice, this list * 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 * of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution. * provided with the distribution.
* *
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * 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 * 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 * 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 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 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 * 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 * 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 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* *
* The views and conclusions contained in the software and documentation are those of the * 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 * authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com. * or implied, of BetaSteward_at_googlemail.com.
*/ */
package mage.game.combat; package mage.game.combat;
import java.io.Serializable; import java.io.Serializable;
@ -57,7 +56,6 @@ import mage.util.CardUtil;
import mage.util.Copyable; import mage.util.Copyable;
import mage.util.trace.TraceUtil; import mage.util.trace.TraceUtil;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
@ -81,7 +79,6 @@ public class Combat implements Serializable, Copyable<Combat> {
private final Map<UUID, Set<UUID>> creaturesForcedToAttack = new HashMap<UUID, Set<UUID>>(); private final Map<UUID, Set<UUID>> creaturesForcedToAttack = new HashMap<UUID, Set<UUID>>();
private int maxAttackers = Integer.MIN_VALUE; private int maxAttackers = Integer.MIN_VALUE;
public Combat() { public Combat() {
this.useToughnessForDamage = false; this.useToughnessForDamage = false;
} }
@ -163,8 +160,8 @@ public class Combat implements Serializable, Copyable<Combat> {
} }
/** /**
* Add an additional attacker to the combat (e.g. token of Geist of Saint Traft) * Add an additional attacker to the combat (e.g. token of Geist of Saint
* This method doesn't trigger ATTACKER_DECLARED event (as intended). * Traft) This method doesn't trigger ATTACKER_DECLARED event (as intended).
* *
* @param creatureId - creature that shall be added to the combat * @param creatureId - creature that shall be added to the combat
* @param game * @param game
@ -175,8 +172,7 @@ public class Combat implements Serializable, Copyable<Combat> {
if (defenders.size() == 1) { if (defenders.size() == 1) {
declareAttacker(creatureId, defenders.iterator().next(), game); declareAttacker(creatureId, defenders.iterator().next(), game);
return true; return true;
} } else {
else {
TargetDefender target = new TargetDefender(defenders, creatureId); TargetDefender target = new TargetDefender(defenders, creatureId);
target.setRequired(true); target.setRequired(true);
player.chooseTarget(Outcome.Damage, target, null, game); player.chooseTarget(Outcome.Damage, target, null, game);
@ -204,8 +200,8 @@ public class Combat implements Serializable, Copyable<Combat> {
public void resumeSelectAttackers(Game game) { public void resumeSelectAttackers(Game game) {
Player player = game.getPlayer(attackerId); Player player = game.getPlayer(attackerId);
for (CombatGroup group: groups) { for (CombatGroup group : groups) {
for (UUID attacker: group.getAttackers()) { for (UUID attacker : group.getAttackers()) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ATTACKER_DECLARED, group.defenderId, attacker, attackerId)); game.fireEvent(GameEvent.getEvent(GameEvent.EventType.ATTACKER_DECLARED, group.defenderId, attacker, attackerId));
} }
} }
@ -217,14 +213,14 @@ public class Combat implements Serializable, Copyable<Combat> {
//20101001 - 508.1d //20101001 - 508.1d
for (Permanent creature : player.getAvailableAttackers(game)) { for (Permanent creature : player.getAvailableAttackers(game)) {
boolean mustAttack = false; boolean mustAttack = false;
Set <UUID> defendersForcedToAttack = new HashSet<UUID>(); Set<UUID> defendersForcedToAttack = new HashSet<UUID>();
for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) {
RequirementEffect effect = (RequirementEffect)entry.getKey(); RequirementEffect effect = (RequirementEffect) entry.getKey();
if (effect.mustAttack(game)) { if (effect.mustAttack(game)) {
mustAttack = true; mustAttack = true;
for (Ability ability: (HashSet<Ability>)entry.getValue()) { for (Ability ability : (HashSet<Ability>) entry.getValue()) {
UUID defenderId = effect.mustAttackDefender(ability, game); UUID defenderId = effect.mustAttackDefender(ability, game);
if(defenderId != null) { if (defenderId != null) {
defendersForcedToAttack.add(defenderId); defendersForcedToAttack.add(defenderId);
} }
break; break;
@ -232,7 +228,7 @@ public class Combat implements Serializable, Copyable<Combat> {
} }
} }
if (mustAttack) { if (mustAttack) {
creaturesForcedToAttack.put(creature.getId(),defendersForcedToAttack); creaturesForcedToAttack.put(creature.getId(), defendersForcedToAttack);
if (defendersForcedToAttack.isEmpty()) { if (defendersForcedToAttack.isEmpty()) {
if (defenders.size() == 1) { if (defenders.size() == 1) {
player.declareAttacker(creature.getId(), defenders.iterator().next(), game); player.declareAttacker(creature.getId(), defenders.iterator().next(), game);
@ -253,16 +249,16 @@ public class Combat implements Serializable, Copyable<Combat> {
protected void checkAttackRestrictions(Player player, Game game) { protected void checkAttackRestrictions(Player player, Game game) {
int count = 0; int count = 0;
for (CombatGroup group: groups) { for (CombatGroup group : groups) {
count += group.getAttackers().size(); count += group.getAttackers().size();
} }
if (count > 1) { if (count > 1) {
List<UUID> tobeRemoved = new ArrayList<UUID>(); List<UUID> tobeRemoved = new ArrayList<UUID>();
for (CombatGroup group: groups) { for (CombatGroup group : groups) {
for (UUID attackingCreatureId: group.getAttackers()) { for (UUID attackingCreatureId : group.getAttackers()) {
Permanent attacker = game.getPermanent(attackingCreatureId); Permanent attacker = game.getPermanent(attackingCreatureId);
if (count >1 && attacker != null && attacker.getAbilities().containsKey(CanAttackOnlyAloneAbility.getInstance().getId())) { if (count > 1 && attacker != null && attacker.getAbilities().containsKey(CanAttackOnlyAloneAbility.getInstance().getId())) {
game.informPlayers(attacker.getName() + " can only attack alone. Removing it from combat."); game.informPlayers(attacker.getName() + " can only attack alone. Removing it from combat.");
tobeRemoved.add(attackingCreatureId); tobeRemoved.add(attackingCreatureId);
count--; count--;
@ -276,8 +272,8 @@ public class Combat implements Serializable, Copyable<Combat> {
if (count == 1) { if (count == 1) {
List<UUID> tobeRemoved = new ArrayList<UUID>(); List<UUID> tobeRemoved = new ArrayList<UUID>();
for (CombatGroup group: groups) { for (CombatGroup group : groups) {
for (UUID attackingCreatureId: group.getAttackers()) { for (UUID attackingCreatureId : group.getAttackers()) {
Permanent attacker = game.getPermanent(attackingCreatureId); Permanent attacker = game.getPermanent(attackingCreatureId);
if (attacker != null && attacker.getAbilities().containsKey(CantAttackAloneAbility.getInstance().getId())) { if (attacker != null && attacker.getAbilities().containsKey(CantAttackAloneAbility.getInstance().getId())) {
game.informPlayers(attacker.getName() + " can't attack alone. Removing it from combat."); game.informPlayers(attacker.getName() + " can't attack alone. Removing it from combat.");
@ -295,40 +291,63 @@ public class Combat implements Serializable, Copyable<Combat> {
public void selectBlockers(Game game) { public void selectBlockers(Game game) {
if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_BLOCKERS, attackerId, attackerId))) { if (!game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_BLOCKERS, attackerId, attackerId))) {
game.getCombat().selectBlockers(null, game);
}
}
// !! Attention: Changes to this block must be also done in card "OdricMaster Tactician". /**
Player player = game.getPlayer(attackerId); * Handle the blocker selection process
*
* @param blockController player that controlls how to block, if null the defender is the controller
* @param game
*/
public void selectBlockers(Player blockController, Game game) {
Player attacker = game.getPlayer(attackerId);
//20101001 - 509.1c //20101001 - 509.1c
checkBlockRequirementsBefore(player, game); this.checkBlockRequirementsBefore(attacker, game);
for (UUID defenderId : getPlayerDefenders(game)) { for (UUID defenderId : getPlayerDefenders(game)) {
boolean choose = true;
Player defender = game.getPlayer(defenderId); Player defender = game.getPlayer(defenderId);
if (defender != null) {
boolean choose = true;
if (blockController == null) {
blockController = defender;
}
while (choose) { while (choose) {
game.getPlayer(defenderId).selectBlockers(game, defenderId); blockController.selectBlockers(game, defenderId);
if (game.isPaused() || game.isGameOver()) { if (game.isPaused() || game.isGameOver()) {
return; return;
} }
if (!checkBlockRestrictions(game.getPlayer(defenderId), game)) { if (!this.checkBlockRestrictions(defender, game)) {
// only human player can decide to do the block in another way if (blockController.isHuman()) { // only human player can decide to do the block in another way
if (defender.isHuman()) {
continue; continue;
} }
} }
choose = !this.checkBlockRequirementsAfter(defender, blockController, game);
choose = !checkBlockRequirementsAfter(defender, defender, game);
} }
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId)); game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId));
// add info about attacker blocked by blocker to the game log // add info about attacker blocked by blocker to the game log
this.logBlockerInfo(defender, game);
}
}
// tool to catch the bug about flyers blocked by non flyers or intimidate blocked by creatures with other colors
TraceUtil.traceCombatIfNeeded(game, this);
}
/**
* Add info about attacker blocked by blocker to the game log
*
*/
private void logBlockerInfo(Player defender, Game game) {
boolean shownDefendingPlayer = false; boolean shownDefendingPlayer = false;
for (CombatGroup group :this.getGroups()) { for (CombatGroup group : this.getGroups()) {
if (group.defendingPlayerId.equals(defenderId)) { if (group.defendingPlayerId.equals(defender.getId())) {
if (!shownDefendingPlayer && defender != null) { if (!shownDefendingPlayer) {
game.informPlayers(new StringBuilder("Attacked player: ").append(defender.getName()).toString()); game.informPlayers(new StringBuilder("Attacked player: ").append(defender.getName()).toString());
shownDefendingPlayer = true; shownDefendingPlayer = true;
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for(UUID attackingCreatureId : group.getAttackers()) { for (UUID attackingCreatureId : group.getAttackers()) {
Permanent attackingCreature = game.getPermanent(attackingCreatureId); Permanent attackingCreature = game.getPermanent(attackingCreatureId);
if (attackingCreature != null) { if (attackingCreature != null) {
sb.append(attackingCreature.getName()).append(" ("); sb.append(attackingCreature.getName()).append(" (");
@ -343,7 +362,7 @@ public class Combat implements Serializable, Copyable<Combat> {
} }
if (group.getBlockers().size() > 0) { if (group.getBlockers().size() > 0) {
sb.append("blocked by "); sb.append("blocked by ");
for(UUID blockingCreatureId : group.getBlockers()) { for (UUID blockingCreatureId : group.getBlockers()) {
Permanent blockingCreature = game.getPermanent(blockingCreatureId); Permanent blockingCreature = game.getPermanent(blockingCreatureId);
if (blockingCreature != null) { if (blockingCreature != null) {
sb.append(blockingCreature.getName()).append(" ("); sb.append(blockingCreature.getName()).append(" (");
@ -351,20 +370,17 @@ public class Combat implements Serializable, Copyable<Combat> {
} }
} }
} else{ } else {
sb.append("unblocked"); sb.append("unblocked");
} }
game.informPlayers(sb.toString()); game.informPlayers(sb.toString());
} }
} }
}
TraceUtil.traceCombatIfNeeded(game, this);
}
} }
/** /**
* Check the block restrictions * Check the block restrictions
*
* @param player * @param player
* @param game * @param game
* @return false - if block restrictions were not complied * @return false - if block restrictions were not complied
@ -372,7 +388,7 @@ public class Combat implements Serializable, Copyable<Combat> {
public boolean checkBlockRestrictions(Player player, Game game) { public boolean checkBlockRestrictions(Player player, Game game) {
int count = 0; int count = 0;
boolean blockWasLegal = true; boolean blockWasLegal = true;
for (CombatGroup group: groups) { for (CombatGroup group : groups) {
count += group.getBlockers().size(); count += group.getBlockers().size();
} }
for (CombatGroup group : groups) { for (CombatGroup group : groups) {
@ -412,9 +428,9 @@ public class Combat implements Serializable, Copyable<Combat> {
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())) { if (game.getOpponents(attackerId).contains(creature.getControllerId())) {
for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) {
RequirementEffect effect = (RequirementEffect)entry.getKey(); RequirementEffect effect = (RequirementEffect) entry.getKey();
if (effect.mustBlock(game)) { if (effect.mustBlock(game)) {
for (Ability ability: (HashSet<Ability>)entry.getValue()) { for (Ability ability : (HashSet<Ability>) entry.getValue()) {
UUID attackId = effect.mustBlockAttacker(ability, game); UUID attackId = effect.mustBlockAttacker(ability, game);
Player defender = game.getPlayer(creature.getControllerId()); Player defender = game.getPlayer(creature.getControllerId());
if (attackId != null && defender != null) { if (attackId != null && defender != null) {
@ -451,9 +467,9 @@ public class Combat implements Serializable, Copyable<Combat> {
if (creature.getBlocking() > 0) { if (creature.getBlocking() > 0) {
// get all requirement effects that apply to the creature (ce.g. is able to block attacker) // get all requirement effects that apply to the creature (ce.g. is able to block attacker)
for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) {
RequirementEffect effect = (RequirementEffect)entry.getKey(); RequirementEffect effect = (RequirementEffect) entry.getKey();
// get possible mustBeBlockedByAtLeastOne blocker // get possible mustBeBlockedByAtLeastOne blocker
for (Ability ability: (HashSet<Ability>)entry.getValue()) { for (Ability ability : (HashSet<Ability>) entry.getValue()) {
UUID toBeBlockedCreature = effect.mustBlockAttackerIfElseUnblocked(ability, game); UUID toBeBlockedCreature = effect.mustBlockAttackerIfElseUnblocked(ability, game);
if (toBeBlockedCreature != null) { if (toBeBlockedCreature != null) {
Set<UUID> potentialBlockers; Set<UUID> potentialBlockers;
@ -473,9 +489,9 @@ public class Combat implements Serializable, Copyable<Combat> {
if (creature.getBlocking() == 0) { if (creature.getBlocking() == 0) {
// get all requirement effects that apply to the creature // get all requirement effects that apply to the creature
for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) { for (Map.Entry entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, game).entrySet()) {
RequirementEffect effect = (RequirementEffect)entry.getKey(); RequirementEffect effect = (RequirementEffect) entry.getKey();
// get possible mustBeBlockedByAtLeastOne blocker // get possible mustBeBlockedByAtLeastOne blocker
for (Ability ability: (HashSet<Ability>)entry.getValue()) { for (Ability ability : (HashSet<Ability>) entry.getValue()) {
UUID toBeBlockedCreature = effect.mustBlockAttackerIfElseUnblocked(ability, game); UUID toBeBlockedCreature = effect.mustBlockAttackerIfElseUnblocked(ability, game);
if (toBeBlockedCreature != null) { if (toBeBlockedCreature != null) {
Set<UUID> potentialBlockers; Set<UUID> potentialBlockers;
@ -499,7 +515,7 @@ public class Combat implements Serializable, Copyable<Combat> {
break; break;
} }
} }
// is so inform human player or set block for AI player // if so inform human player or set block for AI player
if (mayBlock) { if (mayBlock) {
if (controller.isHuman()) { if (controller.isHuman()) {
game.informPlayer(controller, "Creature should block this turn: " + creature.getName()); game.informPlayer(controller, "Creature should block this turn: " + creature.getName());
@ -526,7 +542,7 @@ public class Combat implements Serializable, Copyable<Combat> {
} }
// check attacking creature mustBeBlockedByAtLeastOne // check attacking creature mustBeBlockedByAtLeastOne
for (UUID toBeBlockedCreatureId: mustBeBlockedByAtLeastOne.keySet()) { for (UUID toBeBlockedCreatureId : mustBeBlockedByAtLeastOne.keySet()) {
for (CombatGroup combatGroup : game.getCombat().getGroups()) { for (CombatGroup combatGroup : game.getCombat().getGroups()) {
if (combatGroup.getBlockers().isEmpty() && combatGroup.getAttackers().contains(toBeBlockedCreatureId)) { if (combatGroup.getBlockers().isEmpty() && combatGroup.getAttackers().contains(toBeBlockedCreatureId)) {
// creature is not blocked but has possible blockers // creature is not blocked but has possible blockers
@ -592,7 +608,7 @@ public class Combat implements Serializable, Copyable<Combat> {
} }
// check if creatures are forced to block but do not block at all or block creatures they are not forced to block // check if creatures are forced to block but do not block at all or block creatures they are not forced to block
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (Map.Entry<UUID, Set<UUID>> entry :creaturesForcedToBlockAttackers.entrySet()) { for (Map.Entry<UUID, Set<UUID>> entry : creaturesForcedToBlockAttackers.entrySet()) {
boolean blockIsValid; boolean blockIsValid;
Permanent creatureForcedToBlock = game.getPermanent(entry.getKey()); Permanent creatureForcedToBlock = game.getPermanent(entry.getKey());
if (creatureForcedToBlock == null) { if (creatureForcedToBlock == null) {
@ -607,7 +623,7 @@ public class Combat implements Serializable, Copyable<Combat> {
CombatGroups: CombatGroups:
for (CombatGroup combatGroup : game.getCombat().getGroups()) { for (CombatGroup combatGroup : game.getCombat().getGroups()) {
if (combatGroup.getBlockers().contains(creatureForcedToBlock.getId())) { if (combatGroup.getBlockers().contains(creatureForcedToBlock.getId())) {
for (UUID forcingAttackerId :combatGroup.getAttackers()) { for (UUID forcingAttackerId : combatGroup.getAttackers()) {
if (entry.getValue().contains(forcingAttackerId)) { if (entry.getValue().contains(forcingAttackerId)) {
// the creature is blocking a forcing attacker, so the block is ok // the creature is blocking a forcing attacker, so the block is ok
blockIsValid = true; blockIsValid = true;
@ -706,7 +722,7 @@ public class Combat implements Serializable, Copyable<Combat> {
return false; return false;
} }
CombatGroup newGroup = new CombatGroup(defenderId, defender != null, defender != null ? defender.getControllerId(): defenderId); CombatGroup newGroup = new CombatGroup(defenderId, defender != null, defender != null ? defender.getControllerId() : defenderId);
newGroup.attackers.add(attackerId); newGroup.attackers.add(attackerId);
Permanent attacker = game.getPermanent(attackerId); Permanent attacker = game.getPermanent(attackerId);
if (!attacker.getAbilities().containsKey(VigilanceAbility.getInstance().getId())) { if (!attacker.getAbilities().containsKey(VigilanceAbility.getInstance().getId())) {
@ -969,7 +985,6 @@ public class Combat implements Serializable, Copyable<Combat> {
return maxAttackers; return maxAttackers;
} }
@Override @Override
public Combat copy() { public Combat copy() {
return new Combat(this); return new Combat(this);