GameLog - attack and block are powered by events.

Combat - renamed some methods, extracted aside blocker costs
This commit is contained in:
Maxmtg
2013-06-02 15:08:37 +00:00
parent 2cd5d24d5f
commit bf5bd91e9a
12 changed files with 164 additions and 90 deletions

2
.gitattributes vendored
View File

@@ -14234,7 +14234,9 @@ src/main/java/forge/game/ai/ComputerUtilCost.java -text
src/main/java/forge/game/ai/ComputerUtilMana.java -text src/main/java/forge/game/ai/ComputerUtilMana.java -text
src/main/java/forge/game/event/GameEvent.java -text src/main/java/forge/game/event/GameEvent.java -text
src/main/java/forge/game/event/GameEventAnteCardsSelected.java -text src/main/java/forge/game/event/GameEventAnteCardsSelected.java -text
src/main/java/forge/game/event/GameEventAttackersDeclared.java -text
src/main/java/forge/game/event/GameEventBlockerAssigned.java -text src/main/java/forge/game/event/GameEventBlockerAssigned.java -text
src/main/java/forge/game/event/GameEventBlockersDeclared.java -text
src/main/java/forge/game/event/GameEventCardDamaged.java -text src/main/java/forge/game/event/GameEventCardDamaged.java -text
src/main/java/forge/game/event/GameEventCardDestroyed.java -text src/main/java/forge/game/event/GameEventCardDestroyed.java -text
src/main/java/forge/game/event/GameEventCardDiscarded.java -text src/main/java/forge/game/event/GameEventCardDiscarded.java -text

View File

@@ -24,7 +24,6 @@ import java.util.Observable;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import forge.game.event.IGameEventVisitor; import forge.game.event.IGameEventVisitor;
import forge.game.phase.Combat;
/** /**
@@ -105,15 +104,6 @@ public class GameLog extends Observable {
return result; return result;
} }
public void addCombatAttackers(Combat combat) {
this.add(GameLogFormatter.describeAttack(combat));
}
public void addCombatBlockers(Combat combat) {
this.add(GameLogFormatter.describeBlock(combat));
}
// Special methods
public IGameEventVisitor<?> getEventVisitor() { public IGameEventVisitor<?> getEventVisitor() {
return formatter; return formatter;
} }

View File

@@ -1,6 +1,7 @@
package forge; package forge;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -8,6 +9,8 @@ import com.google.common.eventbus.Subscribe;
import forge.card.spellability.TargetChoices; import forge.card.spellability.TargetChoices;
import forge.game.GameOutcome; import forge.game.GameOutcome;
import forge.game.event.GameEventAttackersDeclared;
import forge.game.event.GameEventBlockersDeclared;
import forge.game.event.GameEventCardDamaged; import forge.game.event.GameEventCardDamaged;
import forge.game.event.GameEventCardDamaged.DamageType; import forge.game.event.GameEventCardDamaged.DamageType;
import forge.game.event.GameEventLandPlayed; import forge.game.event.GameEventLandPlayed;
@@ -21,11 +24,11 @@ import forge.game.event.GameEventGameOutcome;
import forge.game.event.GameEvent; import forge.game.event.GameEvent;
import forge.game.event.GameEventTurnPhase; import forge.game.event.GameEventTurnPhase;
import forge.game.event.GameEventPlayerControl; import forge.game.event.GameEventPlayerControl;
import forge.game.phase.Combat;
import forge.game.player.LobbyPlayer; import forge.game.player.LobbyPlayer;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerStatistics; import forge.game.player.PlayerStatistics;
import forge.util.Lang; import forge.util.Lang;
import forge.util.maps.MapOfLists;
public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> { public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
private final GameLog log; private final GameLog log;
@@ -168,40 +171,42 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
return new GameLogEntry(GameLogEntryType.DAMAGE, message); return new GameLogEntry(GameLogEntryType.DAMAGE, message);
} }
@Override
static GameLogEntry describeAttack(final Combat combat) { public GameLogEntry visit(final GameEventAttackersDeclared ev) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
// Loop through Defenders // Loop through Defenders
// Append Defending Player/Planeswalker // Append Defending Player/Planeswalker
// Not a big fan of the triple nested loop here // Not a big fan of the triple nested loop here
for (GameEntity defender : combat.getDefenders()) { for (Entry<GameEntity, Collection<Card>> kv : ev.attackersMap.entrySet()) {
List<Card> attackers = combat.getAttackersOf(defender); Collection<Card> attackers = kv.getValue();
if (attackers == null || attackers.isEmpty()) { if (attackers == null || attackers.isEmpty()) {
continue; continue;
} }
if ( sb.length() > 0 ) sb.append("\n"); if ( sb.length() > 0 ) sb.append("\n");
sb.append(ev.player).append(" assigned ").append(Lang.joinHomogenous(attackers));
sb.append(combat.getAttackingPlayer()).append(" declared ").append(Lang.joinHomogenous(attackers)); sb.append(" to attack ").append(kv.getKey().toString()).append(".");
sb.append(" to attack ").append(defender.toString()).append(".");
} }
if ( sb.length() == 0 )
sb.append(ev.player).append(" didn't attack this turn.");
return new GameLogEntry(GameLogEntryType.COMBAT, sb.toString()); return new GameLogEntry(GameLogEntryType.COMBAT, sb.toString());
} }
static GameLogEntry describeBlock(final Combat combat) { @Override
public GameLogEntry visit(final GameEventBlockersDeclared ev) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
// Loop through Defenders // Loop through Defenders
// Append Defending Player/Planeswalker // Append Defending Player/Planeswalker
List<Card> blockers = null; Collection<Card> blockers = null;
for (GameEntity defender : combat.getDefenders()) { for (Entry<GameEntity, MapOfLists<Card, Card>> kv : ev.blockers.entrySet()) {
List<Card> attackers = combat.getAttackersOf(defender); GameEntity defender = kv.getKey();
MapOfLists<Card, Card> attackers = kv.getValue();
if (attackers == null || attackers.isEmpty()) { if (attackers == null || attackers.isEmpty()) {
continue; continue;
} }
@@ -209,17 +214,17 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
String controllerName = defender instanceof Card ? ((Card)defender).getController().getName() : defender.getName(); String controllerName = defender instanceof Card ? ((Card)defender).getController().getName() : defender.getName();
boolean firstAttacker = true; boolean firstAttacker = true;
for (final Card attacker : attackers) { for (final Entry<Card, Collection<Card>> att : attackers.entrySet()) {
if ( !firstAttacker ) sb.append("\n"); if ( !firstAttacker ) sb.append("\n");
blockers = combat.getBlockers(attacker); blockers = att.getValue();
if ( blockers.isEmpty() ) { if ( blockers.isEmpty() ) {
sb.append(controllerName).append(" didn't block "); sb.append(controllerName).append(" didn't block ");
} else { } else {
sb.append(controllerName).append(" assigned ").append(Lang.joinHomogenous(blockers)).append(" to block "); sb.append(controllerName).append(" assigned ").append(Lang.joinHomogenous(blockers)).append(" to block ");
} }
sb.append(attacker).append("."); sb.append(att.getKey()).append(".");
firstAttacker = false; firstAttacker = false;
} }
} }

View File

@@ -0,0 +1,31 @@
package forge.game.event;
import forge.Card;
import forge.GameEntity;
import forge.game.player.Player;
import forge.util.maps.MapOfLists;
/**
* TODO: Write javadoc for this type.
*
*/
public class GameEventAttackersDeclared extends GameEvent {
public final Player player;
public final MapOfLists<GameEntity, Card> attackersMap;
public GameEventAttackersDeclared(Player playerTurn, MapOfLists<GameEntity, Card> attackersMap) {
this.player = playerTurn;
this.attackersMap = attackersMap;
}
/* (non-Javadoc)
* @see forge.game.event.GameEvent#visit(forge.game.event.IGameEventVisitor)
*/
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
// TODO Auto-generated method stub
return visitor.visit(this);
}
}

View File

@@ -0,0 +1,27 @@
package forge.game.event;
import java.util.Map;
import forge.Card;
import forge.GameEntity;
import forge.util.maps.MapOfLists;
/**
* TODO: Write javadoc for this type.
*
*/
public class GameEventBlockersDeclared extends GameEvent {
public final Map<GameEntity, MapOfLists<Card, Card>> blockers;
public GameEventBlockersDeclared(Map<GameEntity, MapOfLists<Card, Card>> blockers) {
this.blockers = blockers;
}
@Override
public <T> T visit(IGameEventVisitor<T> visitor) {
// TODO Auto-generated method stub
return visitor.visit(this);
}
}

View File

@@ -5,6 +5,8 @@ package forge.game.event;
* *
*/ */
public interface IGameEventVisitor<T> { public interface IGameEventVisitor<T> {
T visit(GameEventAttackersDeclared event);
T visit(GameEventBlockersDeclared event);
T visit(GameEventBlockerAssigned event); T visit(GameEventBlockerAssigned event);
T visit(GameEventCardDamaged event); T visit(GameEventCardDamaged event);
T visit(GameEventCardDestroyed event); T visit(GameEventCardDestroyed event);
@@ -42,6 +44,8 @@ public interface IGameEventVisitor<T> {
// This is base class for all visitors. // This is base class for all visitors.
public static class Base<T> implements IGameEventVisitor<T>{ public static class Base<T> implements IGameEventVisitor<T>{
public T visit(GameEventAttackersDeclared event) { return null; }
public T visit(GameEventBlockersDeclared event) { return null; }
public T visit(GameEventBlockerAssigned event) { return null; } public T visit(GameEventBlockerAssigned event) { return null; }
public T visit(GameEventCardDamaged event) { return null; } public T visit(GameEventCardDamaged event) { return null; }
public T visit(GameEventCardDestroyed event) { return null; } public T visit(GameEventCardDestroyed event) { return null; }
@@ -76,5 +80,6 @@ public interface IGameEventVisitor<T> {
public T visit(GameEventTurnPhase event) { return null; } public T visit(GameEventTurnPhase event) { return null; }
public T visit(GameEventPlayerDamaged event) { return null; } public T visit(GameEventPlayerDamaged event) { return null; }
} }
} }

View File

@@ -565,7 +565,7 @@ public class Combat {
* verifyCreaturesInPlay. * verifyCreaturesInPlay.
* </p> * </p>
*/ */
public final void verifyCreaturesInPlay() { public final void removeAbsentCombatants() {
final List<Card> all = new ArrayList<Card>(); final List<Card> all = new ArrayList<Card>();
all.addAll(this.getAttackers()); all.addAll(this.getAttackers());
all.addAll(this.getAllBlockers()); all.addAll(this.getAllBlockers());
@@ -582,7 +582,7 @@ public class Combat {
* setUnblocked. * setUnblocked.
* </p> * </p>
*/ */
public final void setUnblocked() { public final void setUnblockedAttackers() {
final List<Card> attacking = this.getAttackers(); final List<Card> attacking = this.getAttackers();
for (final Card attacker : attacking) { for (final Card attacker : attacking) {

View File

@@ -1405,4 +1405,16 @@ public class CombatUtil {
} }
} }
public static void checkAttackOrBlockAlone(Combat combat) {
// Handles removing cards like Mogg Flunkies from combat if group attack
// didn't occur
for (Card c1 : combat.getAttackers()) {
if (c1.hasKeyword("CARDNAME can't attack or block alone.") && c1.isAttacking()) {
if (combat.getAttackers().size() < 2) {
combat.removeFromCombat(c1);
}
}
}
}
} // end class CombatUtil } // end class CombatUtil

View File

@@ -26,12 +26,15 @@ import org.apache.commons.lang.time.StopWatch;
import forge.Card; import forge.Card;
import forge.CardLists; import forge.CardLists;
import forge.FThreads; import forge.FThreads;
import forge.GameEntity;
import forge.Singletons; import forge.Singletons;
import forge.CardPredicates.Presets; import forge.CardPredicates.Presets;
import forge.card.trigger.TriggerType; import forge.card.trigger.TriggerType;
import forge.game.GameAge; import forge.game.GameAge;
import forge.game.Game; import forge.game.Game;
import forge.game.GameType; import forge.game.GameType;
import forge.game.event.GameEventAttackersDeclared;
import forge.game.event.GameEventBlockersDeclared;
import forge.game.event.GameEventPlayerPriority; import forge.game.event.GameEventPlayerPriority;
import forge.game.event.GameEventTurnBegan; import forge.game.event.GameEventTurnBegan;
import forge.game.event.GameEventTurnEnded; import forge.game.event.GameEventTurnEnded;
@@ -44,6 +47,9 @@ import forge.gui.framework.SDisplayUtil;
import forge.gui.match.CMatchUI; import forge.gui.match.CMatchUI;
import forge.gui.match.nonsingleton.VField; import forge.gui.match.nonsingleton.VField;
import forge.properties.ForgePreferences.FPref; import forge.properties.ForgePreferences.FPref;
import forge.util.maps.CollectionSuppliers;
import forge.util.maps.HashMapOfLists;
import forge.util.maps.MapOfLists;
/** /**
@@ -270,21 +276,10 @@ public class PhaseHandler implements java.io.Serializable {
playerTurn.getController().declareAttackers(); playerTurn.getController().declareAttackers();
game.getCombat().verifyCreaturesInPlay(); game.getCombat().removeAbsentCombatants();
CombatUtil.checkAttackOrBlockAlone(game.getCombat());
// Handles removing cards like Mogg Flunkies from combat if group attack
// didn't occur
for (Card c1 : game.getCombat().getAttackers()) {
if (c1.hasKeyword("CARDNAME can't attack or block alone.") && c1.isAttacking()) {
if (game.getCombat().getAttackers().size() < 2) {
game.getCombat().removeFromCombat(c1);
}
}
}
// TODO move propaganda to happen as the Attacker is Declared
// TODO move propaganda to happen as the Attacker is Declared
for (final Card c2 : game.getCombat().getAttackers()) { for (final Card c2 : game.getCombat().getAttackers()) {
boolean canAttack = CombatUtil.checkPropagandaEffects(game, c2); boolean canAttack = CombatUtil.checkPropagandaEffects(game, c2);
if ( canAttack ) { if ( canAttack ) {
@@ -294,10 +289,13 @@ public class PhaseHandler implements java.io.Serializable {
game.getCombat().removeFromCombat(c2); game.getCombat().removeFromCombat(c2);
} }
} }
// Then run other Attacker bonuses // Prepare and fire event 'attackers declared'
// check for exalted: MapOfLists<GameEntity, Card> attackersMap = new HashMapOfLists<GameEntity, Card>(CollectionSuppliers.<Card>arrayLists());
for(GameEntity ge : game.getCombat().getDefenders()) attackersMap.addAll(ge, game.getCombat().getAttackersOf(ge));
game.fireEvent(new GameEventAttackersDeclared(playerTurn, attackersMap));
// This Exalted handler should be converted to script
if (game.getCombat().getAttackers().size() == 1) { if (game.getCombat().getAttackers().size() == 1) {
final Player attackingPlayer = game.getCombat().getAttackingPlayer(); final Player attackingPlayer = game.getCombat().getAttackingPlayer();
final Card attacker = game.getCombat().getAttackers().get(0); final Card attacker = game.getCombat().getAttackers().get(0);
@@ -305,7 +303,6 @@ public class PhaseHandler implements java.io.Serializable {
int exaltedMagnitude = card.getKeywordAmount("Exalted"); int exaltedMagnitude = card.getKeywordAmount("Exalted");
if (exaltedMagnitude > 0) { if (exaltedMagnitude > 0) {
CombatUtil.executeExaltedAbility(game, attacker, exaltedMagnitude, card); CombatUtil.executeExaltedAbility(game, attacker, exaltedMagnitude, card);
// Make sure exalted effects get applied only once per combat
} }
if ("Sovereigns of Lost Alara".equals(card.getName())) { if ("Sovereigns of Lost Alara".equals(card.getName())) {
@@ -314,8 +311,7 @@ public class PhaseHandler implements java.io.Serializable {
} }
} }
game.getGameLog().addCombatAttackers(game.getCombat()); // fire trigger
final HashMap<String, Object> runParams = new HashMap<String, Object>(); final HashMap<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Attackers", game.getCombat().getAttackers()); runParams.put("Attackers", game.getCombat().getAttackers());
runParams.put("AttackingPlayer", game.getCombat().getAttackingPlayer()); runParams.put("AttackingPlayer", game.getCombat().getAttackingPlayer());
@@ -325,14 +321,14 @@ public class PhaseHandler implements java.io.Serializable {
CombatUtil.checkDeclareAttackers(game, c); CombatUtil.checkDeclareAttackers(game, c);
} }
game.getStack().unfreezeStack(); game.getStack().unfreezeStack();
this.bCombat = !game.getCombat().getAttackers().isEmpty(); this.bCombat = !game.getCombat().getAttackers().isEmpty();
this.nCombatsThisTurn++; this.nCombatsThisTurn++;
break; break;
case COMBAT_DECLARE_BLOCKERS: case COMBAT_DECLARE_BLOCKERS:
game.getCombat().verifyCreaturesInPlay(); game.getCombat().removeAbsentCombatants();
game.getStack().freezeStack(); game.getStack().freezeStack();
Player p = playerTurn; Player p = playerTurn;
@@ -342,12 +338,25 @@ public class PhaseHandler implements java.io.Serializable {
p.getController().declareBlockers(); p.getController().declareBlockers();
} while(p != playerTurn); } while(p != playerTurn);
game.getStack().unfreezeStack(); game.getCombat().removeAbsentCombatants();
PhaseUtil.handleDeclareBlockers(game); PhaseUtil.handleDeclareBlockers(game);
// map: defender => (many) attacker => (many) blocker
Map<GameEntity, MapOfLists<Card, Card>> blockers = new HashMap<GameEntity, MapOfLists<Card,Card>>();
for(GameEntity ge : game.getCombat().getDefenders()) {
MapOfLists<Card, Card> protectThisDefender = new HashMapOfLists<Card, Card>(CollectionSuppliers.<Card>arrayLists());
for(Card att : game.getCombat().getAttackersOf(ge)) {
protectThisDefender.addAll(att, game.getCombat().getBlockers(att));
}
blockers.put(ge, protectThisDefender);
}
game.fireEvent(new GameEventBlockersDeclared(blockers));
game.getStack().unfreezeStack();
break; break;
case COMBAT_FIRST_STRIKE_DAMAGE: case COMBAT_FIRST_STRIKE_DAMAGE:
game.getCombat().verifyCreaturesInPlay(); game.getCombat().removeAbsentCombatants();
// no first strikers, skip this step // no first strikers, skip this step
if (!game.getCombat().assignCombatDamage(true)) { if (!game.getCombat().assignCombatDamage(true)) {
@@ -359,7 +368,7 @@ public class PhaseHandler implements java.io.Serializable {
break; break;
case COMBAT_DAMAGE: case COMBAT_DAMAGE:
game.getCombat().verifyCreaturesInPlay(); game.getCombat().removeAbsentCombatants();
if (!game.getCombat().assignCombatDamage(false)) { if (!game.getCombat().assignCombatDamage(false)) {
this.givePriorityToPlayer = false; this.givePriorityToPlayer = false;

View File

@@ -15,9 +15,7 @@ public enum PhaseType {
MAIN1("Main, precombat", "Main1"), MAIN1("Main, precombat", "Main1"),
COMBAT_BEGIN("Begin Combat", "BeginCombat"), COMBAT_BEGIN("Begin Combat", "BeginCombat"),
COMBAT_DECLARE_ATTACKERS("Declare Attackers"), COMBAT_DECLARE_ATTACKERS("Declare Attackers"),
//COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY("Declare Attackers - Play Instants and Abilities"),
COMBAT_DECLARE_BLOCKERS("Declare Blockers"), COMBAT_DECLARE_BLOCKERS("Declare Blockers"),
//COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY("Declare Blockers - Play Instants and Abilities"),
COMBAT_FIRST_STRIKE_DAMAGE("First Strike Damage"), COMBAT_FIRST_STRIKE_DAMAGE("First Strike Damage"),
COMBAT_DAMAGE("Combat Damage"), COMBAT_DAMAGE("Combat Damage"),
COMBAT_END("End Combat", "EndCombat"), COMBAT_END("End Combat", "EndCombat"),
@@ -27,11 +25,11 @@ public enum PhaseType {
public static final List<PhaseType> ALL_PHASES = Collections.unmodifiableList( public static final List<PhaseType> ALL_PHASES = Collections.unmodifiableList(
Arrays.asList( Arrays.asList(
UNTAP, UPKEEP, DRAW, MAIN1, UNTAP, UPKEEP, DRAW,
COMBAT_BEGIN, COMBAT_DECLARE_ATTACKERS, //COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY, MAIN1,
COMBAT_DECLARE_BLOCKERS, // COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY, COMBAT_BEGIN, COMBAT_DECLARE_ATTACKERS, COMBAT_DECLARE_BLOCKERS, COMBAT_FIRST_STRIKE_DAMAGE, COMBAT_DAMAGE, COMBAT_END,
COMBAT_FIRST_STRIKE_DAMAGE, COMBAT_DAMAGE, COMBAT_END, MAIN2,
MAIN2, END_OF_TURN, CLEANUP END_OF_TURN, CLEANUP
) )
); );

View File

@@ -18,7 +18,6 @@
package forge.game.phase; package forge.game.phase;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
@@ -28,7 +27,6 @@ import forge.CardLists;
import forge.card.cost.Cost; import forge.card.cost.Cost;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.card.staticability.StaticAbility; import forge.card.staticability.StaticAbility;
import forge.card.trigger.TriggerType;
import forge.game.Game; import forge.game.Game;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerController.ManaPaymentPurpose; import forge.game.player.PlayerController.ManaPaymentPurpose;
@@ -77,7 +75,6 @@ public class PhaseUtil {
*/ */
public static void handleDeclareBlockers(Game game) { public static void handleDeclareBlockers(Game game) {
final Combat combat = game.getCombat(); final Combat combat = game.getCombat();
combat.verifyCreaturesInPlay();
// Handles removing cards like Mogg Flunkies from combat if group block // Handles removing cards like Mogg Flunkies from combat if group block
// didn't occur // didn't occur
@@ -85,21 +82,7 @@ public class PhaseUtil {
for (Card blocker : filterList) { for (Card blocker : filterList) {
final List<Card> attackers = new ArrayList<Card>(combat.getAttackersBlockedBy(blocker)); final List<Card> attackers = new ArrayList<Card>(combat.getAttackersBlockedBy(blocker));
for (Card attacker : attackers) { for (Card attacker : attackers) {
Cost blockCost = new Cost(ManaCost.ZERO, true); boolean hasPaid = payRequiredBlockCosts(game, blocker, attacker);
// Sort abilities to apply them in proper order
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
final ArrayList<StaticAbility> staticAbilities = card.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
Cost c1 = stAb.getBlockCost(blocker, attacker);
if ( c1 != null )
blockCost.add(c1);
}
}
boolean hasPaid = blockCost.getTotalMana().isZero() && blockCost.isOnlyManaCost(); // true if needless to pay
if (!hasPaid) {
hasPaid = blocker.getController().getController().payManaOptional(blocker, blockCost, "Pay cost to declare " + blocker + " a blocker", ManaPaymentPurpose.DeclareBlocker);
}
if ( !hasPaid ) { if ( !hasPaid ) {
combat.removeBlockAssignment(attacker, blocker); combat.removeBlockAssignment(attacker, blocker);
@@ -114,12 +97,9 @@ public class PhaseUtil {
} }
} }
game.getStack().freezeStack(); combat.setUnblockedAttackers();
combat.setUnblocked(); List<Card> list = combat.getAllBlockers();
List<Card> list = new ArrayList<Card>();
list.addAll(combat.getAllBlockers());
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
@@ -128,16 +108,30 @@ public class PhaseUtil {
} }
}); });
final List<Card> attList = combat.getAttackers();
CombatUtil.checkDeclareBlockers(game, list); CombatUtil.checkDeclareBlockers(game, list);
for (final Card a : attList) { for (final Card a : combat.getAttackers()) {
CombatUtil.checkBlockedAttackers(game, a, combat.getBlockers(a)); CombatUtil.checkBlockedAttackers(game, a, combat.getBlockers(a));
} }
}
game.getStack().unfreezeStack();
game.getGameLog().addCombatBlockers(game.getCombat()); private static boolean payRequiredBlockCosts(Game game, Card blocker, Card attacker) {
Cost blockCost = new Cost(ManaCost.ZERO, true);
// Sort abilities to apply them in proper order
for (Card card : game.getCardsIn(ZoneType.Battlefield)) {
final ArrayList<StaticAbility> staticAbilities = card.getStaticAbilities();
for (final StaticAbility stAb : staticAbilities) {
Cost c1 = stAb.getBlockCost(blocker, attacker);
if ( c1 != null )
blockCost.add(c1);
}
}
boolean hasPaid = blockCost.getTotalMana().isZero() && blockCost.isOnlyManaCost(); // true if needless to pay
if (!hasPaid) {
hasPaid = blocker.getController().getController().payManaOptional(blocker, blockCost, "Pay cost to declare " + blocker + " a blocker", ManaPaymentPurpose.DeclareBlocker);
}
return hasPaid;
} }
} }

View File

@@ -1,5 +1,6 @@
package forge.util; package forge.util;
import java.util.Collection;
import java.util.List; import java.util.List;
import com.google.common.base.Function; import com.google.common.base.Function;
@@ -27,8 +28,8 @@ public class Lang {
} }
} }
public static <T> String joinHomogenous(List<T> objects) { return joinHomogenous(objects, null); } public static <T> String joinHomogenous(Collection<T> objects) { return joinHomogenous(objects, null); }
public static <T> String joinHomogenous(List<T> objects, Function<T, String> accessor) { public static <T> String joinHomogenous(Collection<T> objects, Function<T, String> accessor) {
int remaining = objects.size(); int remaining = objects.size();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for(T obj : objects) { for(T obj : objects) {