From bf5bd91e9adb9e6bdbb254a6c948f81dce1819ce Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 2 Jun 2013 15:08:37 +0000 Subject: [PATCH] GameLog - attack and block are powered by events. Combat - renamed some methods, extracted aside blocker costs --- .gitattributes | 2 + src/main/java/forge/GameLog.java | 10 ---- src/main/java/forge/GameLogFormatter.java | 37 +++++++----- .../event/GameEventAttackersDeclared.java | 31 ++++++++++ .../game/event/GameEventBlockersDeclared.java | 27 +++++++++ .../forge/game/event/IGameEventVisitor.java | 5 ++ src/main/java/forge/game/phase/Combat.java | 4 +- .../java/forge/game/phase/CombatUtil.java | 12 ++++ .../java/forge/game/phase/PhaseHandler.java | 59 +++++++++++-------- src/main/java/forge/game/phase/PhaseType.java | 12 ++-- src/main/java/forge/game/phase/PhaseUtil.java | 50 +++++++--------- src/main/java/forge/util/Lang.java | 5 +- 12 files changed, 164 insertions(+), 90 deletions(-) create mode 100644 src/main/java/forge/game/event/GameEventAttackersDeclared.java create mode 100644 src/main/java/forge/game/event/GameEventBlockersDeclared.java diff --git a/.gitattributes b/.gitattributes index f2462ace38e..8f236249a19 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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/event/GameEvent.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/GameEventBlockersDeclared.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/GameEventCardDiscarded.java -text diff --git a/src/main/java/forge/GameLog.java b/src/main/java/forge/GameLog.java index fde026a5f2e..1719426d697 100644 --- a/src/main/java/forge/GameLog.java +++ b/src/main/java/forge/GameLog.java @@ -24,7 +24,6 @@ import java.util.Observable; import org.apache.commons.lang3.StringUtils; import forge.game.event.IGameEventVisitor; -import forge.game.phase.Combat; /** @@ -105,15 +104,6 @@ public class GameLog extends Observable { 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() { return formatter; } diff --git a/src/main/java/forge/GameLogFormatter.java b/src/main/java/forge/GameLogFormatter.java index 40851a196d0..fb50464d536 100644 --- a/src/main/java/forge/GameLogFormatter.java +++ b/src/main/java/forge/GameLogFormatter.java @@ -1,6 +1,7 @@ package forge; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map.Entry; @@ -8,6 +9,8 @@ import com.google.common.eventbus.Subscribe; import forge.card.spellability.TargetChoices; import forge.game.GameOutcome; +import forge.game.event.GameEventAttackersDeclared; +import forge.game.event.GameEventBlockersDeclared; import forge.game.event.GameEventCardDamaged; import forge.game.event.GameEventCardDamaged.DamageType; import forge.game.event.GameEventLandPlayed; @@ -21,11 +24,11 @@ import forge.game.event.GameEventGameOutcome; import forge.game.event.GameEvent; import forge.game.event.GameEventTurnPhase; import forge.game.event.GameEventPlayerControl; -import forge.game.phase.Combat; import forge.game.player.LobbyPlayer; import forge.game.player.Player; import forge.game.player.PlayerStatistics; import forge.util.Lang; +import forge.util.maps.MapOfLists; public class GameLogFormatter extends IGameEventVisitor.Base { private final GameLog log; @@ -168,40 +171,42 @@ public class GameLogFormatter extends IGameEventVisitor.Base { return new GameLogEntry(GameLogEntryType.DAMAGE, message); } - - static GameLogEntry describeAttack(final Combat combat) { + @Override + public GameLogEntry visit(final GameEventAttackersDeclared ev) { final StringBuilder sb = new StringBuilder(); // Loop through Defenders // Append Defending Player/Planeswalker // Not a big fan of the triple nested loop here - for (GameEntity defender : combat.getDefenders()) { - List attackers = combat.getAttackersOf(defender); + for (Entry> kv : ev.attackersMap.entrySet()) { + Collection attackers = kv.getValue(); if (attackers == null || attackers.isEmpty()) { continue; } if ( sb.length() > 0 ) sb.append("\n"); - - sb.append(combat.getAttackingPlayer()).append(" declared ").append(Lang.joinHomogenous(attackers)); - sb.append(" to attack ").append(defender.toString()).append("."); + sb.append(ev.player).append(" assigned ").append(Lang.joinHomogenous(attackers)); + sb.append(" to attack ").append(kv.getKey().toString()).append("."); } + if ( sb.length() == 0 ) + sb.append(ev.player).append(" didn't attack this turn."); 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(); // Loop through Defenders // Append Defending Player/Planeswalker - List blockers = null; - + Collection blockers = null; - for (GameEntity defender : combat.getDefenders()) { - List attackers = combat.getAttackersOf(defender); + for (Entry> kv : ev.blockers.entrySet()) { + GameEntity defender = kv.getKey(); + MapOfLists attackers = kv.getValue(); if (attackers == null || attackers.isEmpty()) { continue; } @@ -209,17 +214,17 @@ public class GameLogFormatter extends IGameEventVisitor.Base { String controllerName = defender instanceof Card ? ((Card)defender).getController().getName() : defender.getName(); boolean firstAttacker = true; - for (final Card attacker : attackers) { + for (final Entry> att : attackers.entrySet()) { if ( !firstAttacker ) sb.append("\n"); - blockers = combat.getBlockers(attacker); + blockers = att.getValue(); if ( blockers.isEmpty() ) { sb.append(controllerName).append(" didn't block "); } else { sb.append(controllerName).append(" assigned ").append(Lang.joinHomogenous(blockers)).append(" to block "); } - sb.append(attacker).append("."); + sb.append(att.getKey()).append("."); firstAttacker = false; } } diff --git a/src/main/java/forge/game/event/GameEventAttackersDeclared.java b/src/main/java/forge/game/event/GameEventAttackersDeclared.java new file mode 100644 index 00000000000..a3501d3be0d --- /dev/null +++ b/src/main/java/forge/game/event/GameEventAttackersDeclared.java @@ -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 attackersMap; + + public GameEventAttackersDeclared(Player playerTurn, MapOfLists attackersMap) { + this.player = playerTurn; + this.attackersMap = attackersMap; + } + + /* (non-Javadoc) + * @see forge.game.event.GameEvent#visit(forge.game.event.IGameEventVisitor) + */ + @Override + public T visit(IGameEventVisitor visitor) { + // TODO Auto-generated method stub + return visitor.visit(this); + } + +} diff --git a/src/main/java/forge/game/event/GameEventBlockersDeclared.java b/src/main/java/forge/game/event/GameEventBlockersDeclared.java new file mode 100644 index 00000000000..f24c8e3d43c --- /dev/null +++ b/src/main/java/forge/game/event/GameEventBlockersDeclared.java @@ -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> blockers; + + public GameEventBlockersDeclared(Map> blockers) { + this.blockers = blockers; + } + + @Override + public T visit(IGameEventVisitor visitor) { + // TODO Auto-generated method stub + return visitor.visit(this); + } + +} diff --git a/src/main/java/forge/game/event/IGameEventVisitor.java b/src/main/java/forge/game/event/IGameEventVisitor.java index 2bd0f9069a0..0a34fee31ce 100644 --- a/src/main/java/forge/game/event/IGameEventVisitor.java +++ b/src/main/java/forge/game/event/IGameEventVisitor.java @@ -5,6 +5,8 @@ package forge.game.event; * */ public interface IGameEventVisitor { + T visit(GameEventAttackersDeclared event); + T visit(GameEventBlockersDeclared event); T visit(GameEventBlockerAssigned event); T visit(GameEventCardDamaged event); T visit(GameEventCardDestroyed event); @@ -42,6 +44,8 @@ public interface IGameEventVisitor { // This is base class for all visitors. public static class Base implements IGameEventVisitor{ + public T visit(GameEventAttackersDeclared event) { return null; } + public T visit(GameEventBlockersDeclared event) { return null; } public T visit(GameEventBlockerAssigned event) { return null; } public T visit(GameEventCardDamaged event) { return null; } public T visit(GameEventCardDestroyed event) { return null; } @@ -76,5 +80,6 @@ public interface IGameEventVisitor { public T visit(GameEventTurnPhase event) { return null; } public T visit(GameEventPlayerDamaged event) { return null; } } + } diff --git a/src/main/java/forge/game/phase/Combat.java b/src/main/java/forge/game/phase/Combat.java index b82beb71612..af69ef7eb4e 100644 --- a/src/main/java/forge/game/phase/Combat.java +++ b/src/main/java/forge/game/phase/Combat.java @@ -565,7 +565,7 @@ public class Combat { * verifyCreaturesInPlay. *

*/ - public final void verifyCreaturesInPlay() { + public final void removeAbsentCombatants() { final List all = new ArrayList(); all.addAll(this.getAttackers()); all.addAll(this.getAllBlockers()); @@ -582,7 +582,7 @@ public class Combat { * setUnblocked. *

*/ - public final void setUnblocked() { + public final void setUnblockedAttackers() { final List attacking = this.getAttackers(); for (final Card attacker : attacking) { diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index 3cdaf590b8e..4a359c55272 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -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 diff --git a/src/main/java/forge/game/phase/PhaseHandler.java b/src/main/java/forge/game/phase/PhaseHandler.java index 02ab957cf64..6d98689f778 100644 --- a/src/main/java/forge/game/phase/PhaseHandler.java +++ b/src/main/java/forge/game/phase/PhaseHandler.java @@ -26,12 +26,15 @@ import org.apache.commons.lang.time.StopWatch; import forge.Card; import forge.CardLists; import forge.FThreads; +import forge.GameEntity; import forge.Singletons; import forge.CardPredicates.Presets; import forge.card.trigger.TriggerType; import forge.game.GameAge; import forge.game.Game; import forge.game.GameType; +import forge.game.event.GameEventAttackersDeclared; +import forge.game.event.GameEventBlockersDeclared; import forge.game.event.GameEventPlayerPriority; import forge.game.event.GameEventTurnBegan; import forge.game.event.GameEventTurnEnded; @@ -44,6 +47,9 @@ import forge.gui.framework.SDisplayUtil; import forge.gui.match.CMatchUI; import forge.gui.match.nonsingleton.VField; 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(); - game.getCombat().verifyCreaturesInPlay(); - - // 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 + game.getCombat().removeAbsentCombatants(); + CombatUtil.checkAttackOrBlockAlone(game.getCombat()); + // TODO move propaganda to happen as the Attacker is Declared for (final Card c2 : game.getCombat().getAttackers()) { boolean canAttack = CombatUtil.checkPropagandaEffects(game, c2); if ( canAttack ) { @@ -294,10 +289,13 @@ public class PhaseHandler implements java.io.Serializable { game.getCombat().removeFromCombat(c2); } } - - // Then run other Attacker bonuses - // check for exalted: + // Prepare and fire event 'attackers declared' + MapOfLists attackersMap = new HashMapOfLists(CollectionSuppliers.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) { final Player attackingPlayer = game.getCombat().getAttackingPlayer(); final Card attacker = game.getCombat().getAttackers().get(0); @@ -305,7 +303,6 @@ public class PhaseHandler implements java.io.Serializable { int exaltedMagnitude = card.getKeywordAmount("Exalted"); if (exaltedMagnitude > 0) { CombatUtil.executeExaltedAbility(game, attacker, exaltedMagnitude, card); - // Make sure exalted effects get applied only once per combat } 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 runParams = new HashMap(); runParams.put("Attackers", game.getCombat().getAttackers()); runParams.put("AttackingPlayer", game.getCombat().getAttackingPlayer()); @@ -325,14 +321,14 @@ public class PhaseHandler implements java.io.Serializable { CombatUtil.checkDeclareAttackers(game, c); } game.getStack().unfreezeStack(); - + this.bCombat = !game.getCombat().getAttackers().isEmpty(); this.nCombatsThisTurn++; break; case COMBAT_DECLARE_BLOCKERS: - game.getCombat().verifyCreaturesInPlay(); + game.getCombat().removeAbsentCombatants(); game.getStack().freezeStack(); Player p = playerTurn; @@ -342,12 +338,25 @@ public class PhaseHandler implements java.io.Serializable { p.getController().declareBlockers(); } while(p != playerTurn); - game.getStack().unfreezeStack(); + game.getCombat().removeAbsentCombatants(); PhaseUtil.handleDeclareBlockers(game); + + // map: defender => (many) attacker => (many) blocker + Map> blockers = new HashMap>(); + for(GameEntity ge : game.getCombat().getDefenders()) { + MapOfLists protectThisDefender = new HashMapOfLists(CollectionSuppliers.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; case COMBAT_FIRST_STRIKE_DAMAGE: - game.getCombat().verifyCreaturesInPlay(); + game.getCombat().removeAbsentCombatants(); // no first strikers, skip this step if (!game.getCombat().assignCombatDamage(true)) { @@ -359,7 +368,7 @@ public class PhaseHandler implements java.io.Serializable { break; case COMBAT_DAMAGE: - game.getCombat().verifyCreaturesInPlay(); + game.getCombat().removeAbsentCombatants(); if (!game.getCombat().assignCombatDamage(false)) { this.givePriorityToPlayer = false; diff --git a/src/main/java/forge/game/phase/PhaseType.java b/src/main/java/forge/game/phase/PhaseType.java index 9cadf7094a6..bad75fca00c 100644 --- a/src/main/java/forge/game/phase/PhaseType.java +++ b/src/main/java/forge/game/phase/PhaseType.java @@ -15,9 +15,7 @@ public enum PhaseType { MAIN1("Main, precombat", "Main1"), COMBAT_BEGIN("Begin Combat", "BeginCombat"), COMBAT_DECLARE_ATTACKERS("Declare Attackers"), - //COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY("Declare Attackers - Play Instants and Abilities"), COMBAT_DECLARE_BLOCKERS("Declare Blockers"), - //COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY("Declare Blockers - Play Instants and Abilities"), COMBAT_FIRST_STRIKE_DAMAGE("First Strike Damage"), COMBAT_DAMAGE("Combat Damage"), COMBAT_END("End Combat", "EndCombat"), @@ -27,11 +25,11 @@ public enum PhaseType { public static final List ALL_PHASES = Collections.unmodifiableList( Arrays.asList( - UNTAP, UPKEEP, DRAW, MAIN1, - COMBAT_BEGIN, COMBAT_DECLARE_ATTACKERS, //COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY, - COMBAT_DECLARE_BLOCKERS, // COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY, - COMBAT_FIRST_STRIKE_DAMAGE, COMBAT_DAMAGE, COMBAT_END, - MAIN2, END_OF_TURN, CLEANUP + UNTAP, UPKEEP, DRAW, + MAIN1, + COMBAT_BEGIN, COMBAT_DECLARE_ATTACKERS, COMBAT_DECLARE_BLOCKERS, COMBAT_FIRST_STRIKE_DAMAGE, COMBAT_DAMAGE, COMBAT_END, + MAIN2, + END_OF_TURN, CLEANUP ) ); diff --git a/src/main/java/forge/game/phase/PhaseUtil.java b/src/main/java/forge/game/phase/PhaseUtil.java index b81ca72cc6a..a4aa69e5e9f 100644 --- a/src/main/java/forge/game/phase/PhaseUtil.java +++ b/src/main/java/forge/game/phase/PhaseUtil.java @@ -18,7 +18,6 @@ package forge.game.phase; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import com.google.common.base.Predicate; @@ -28,7 +27,6 @@ import forge.CardLists; import forge.card.cost.Cost; import forge.card.mana.ManaCost; import forge.card.staticability.StaticAbility; -import forge.card.trigger.TriggerType; import forge.game.Game; import forge.game.player.Player; import forge.game.player.PlayerController.ManaPaymentPurpose; @@ -77,7 +75,6 @@ public class PhaseUtil { */ public static void handleDeclareBlockers(Game game) { final Combat combat = game.getCombat(); - combat.verifyCreaturesInPlay(); // Handles removing cards like Mogg Flunkies from combat if group block // didn't occur @@ -85,21 +82,7 @@ public class PhaseUtil { for (Card blocker : filterList) { final List attackers = new ArrayList(combat.getAttackersBlockedBy(blocker)); for (Card attacker : attackers) { - Cost blockCost = new Cost(ManaCost.ZERO, true); - // Sort abilities to apply them in proper order - for (Card card : game.getCardsIn(ZoneType.Battlefield)) { - final ArrayList 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); - } + boolean hasPaid = payRequiredBlockCosts(game, blocker, attacker); if ( !hasPaid ) { combat.removeBlockAssignment(attacker, blocker); @@ -114,12 +97,9 @@ public class PhaseUtil { } } - game.getStack().freezeStack(); + combat.setUnblockedAttackers(); - combat.setUnblocked(); - - List list = new ArrayList(); - list.addAll(combat.getAllBlockers()); + List list = combat.getAllBlockers(); list = CardLists.filter(list, new Predicate() { @Override @@ -128,16 +108,30 @@ public class PhaseUtil { } }); - final List attList = combat.getAttackers(); - CombatUtil.checkDeclareBlockers(game, list); - for (final Card a : attList) { + for (final Card a : combat.getAttackers()) { 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 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; } } diff --git a/src/main/java/forge/util/Lang.java b/src/main/java/forge/util/Lang.java index 9a85126b405..291a23635d5 100644 --- a/src/main/java/forge/util/Lang.java +++ b/src/main/java/forge/util/Lang.java @@ -1,5 +1,6 @@ package forge.util; +import java.util.Collection; import java.util.List; import com.google.common.base.Function; @@ -27,8 +28,8 @@ public class Lang { } } - public static String joinHomogenous(List objects) { return joinHomogenous(objects, null); } - public static String joinHomogenous(List objects, Function accessor) { + public static String joinHomogenous(Collection objects) { return joinHomogenous(objects, null); } + public static String joinHomogenous(Collection objects, Function accessor) { int remaining = objects.size(); StringBuilder sb = new StringBuilder(); for(T obj : objects) {