From df3b938ed04579ade33cb2e42f200fd59e9279d5 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 7 Jul 2013 22:09:20 +0000 Subject: [PATCH] changed order or actions performed on declare blockers step: declare, pay extra costs, fire event, then fire triggers EventVisualizer.java will be able to play sounds when ai is blocking --- src/main/java/forge/game/combat/Combat.java | 12 +- .../java/forge/game/combat/CombatUtil.java | 109 ++++----------- .../game/event/GameEventBlockersDeclared.java | 22 ++- .../java/forge/game/phase/PhaseHandler.java | 127 +++++++++++------- .../java/forge/sound/EventVisualizer.java | 21 ++- 5 files changed, 154 insertions(+), 137 deletions(-) diff --git a/src/main/java/forge/game/combat/Combat.java b/src/main/java/forge/game/combat/Combat.java index 2677f76050c..f4df348d343 100644 --- a/src/main/java/forge/game/combat/Combat.java +++ b/src/main/java/forge/game/combat/Combat.java @@ -81,6 +81,16 @@ public class Combat { return Lists.newArrayList(attackedEntities.keySet()); } + public final List getDefendersControlledBy(Player who) { + List res = Lists.newArrayList(); + for(GameEntity ge : attackedEntities.keySet()) { + // if defender is the player himself or his cards + if (ge == who || ge instanceof Card && ((Card) ge).getController() == who) + res.add(ge); + } + return res; + } + public final List getDefendingPlaneswalkers() { final List pwDefending = new ArrayList(); for (final GameEntity o : attackedEntities.keySet()) { @@ -389,7 +399,7 @@ public class Combat { // Call this method right after turn-based action of declare blockers has been performed - public final void onBlockersDeclared() { + public final void fireTriggersForUnblockedAttackers() { for(Collection abs : attackedEntities.values()) { for(AttackingBand ab : abs) { Collection blockers = blockedBands.get(ab); diff --git a/src/main/java/forge/game/combat/CombatUtil.java b/src/main/java/forge/game/combat/CombatUtil.java index a5b7b38b780..3fa63b2211b 100644 --- a/src/main/java/forge/game/combat/CombatUtil.java +++ b/src/main/java/forge/game/combat/CombatUtil.java @@ -20,9 +20,6 @@ package forge.game.combat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import org.apache.commons.lang3.StringUtils; import com.google.common.base.Predicate; @@ -959,85 +956,27 @@ public class CombatUtil { c.getController().incrementAttackersDeclaredThisTurn(); } // checkDeclareAttackers - /** - *

- * checkDeclareBlockers. - *

- * @param game - * - * @param cl - * a {@link forge.CardList} object. - */ - public static void checkDeclareBlockers(Game game, final List cl, Combat combat) { - for (final Card c : cl) { - if (!c.getDamageHistory().getCreatureBlockedThisCombat()) { - for (final SpellAbility ab : CardFactoryUtil.getBushidoEffects(c)) { - game.getStack().add(ab); - } - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Blocker", c); - final Card attacker = combat.getAttackersBlockedBy(c).get(0); - runParams.put("Attacker", attacker); - game.getTriggerHandler().runTrigger(TriggerType.Blocks, runParams, false); + + public static void handleRampage(final Game game, final Card a, final List blockers) { + for (final String keyword : a.getKeyword()) { + int idx = keyword.indexOf("Rampage "); + if ( idx < 0) + continue; + + final int numBlockers = blockers.size(); + if (numBlockers > 1) { + final int magnitude = Integer.valueOf(keyword.substring(idx + "Rampage ".length())); + CombatUtil.executeRampageAbility(game, a, magnitude, numBlockers); } + } // end Rampage + } - c.getDamageHistory().setCreatureBlockedThisCombat(true); - } // for - - } // checkDeclareBlockers - - /** - *

- * checkBlockedAttackers. - *

- * @param game - * - * @param a - * a {@link forge.Card} object. - * @param b - * a {@link forge.Card} object. - */ - public static void checkBlockedAttackers(final Game game, final Card a, final List blockers) { - if (blockers.isEmpty()) { - return; - } - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Attacker", a); - runParams.put("Blockers", blockers); - runParams.put("NumBlockers", blockers.size()); - game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false); - - if (!a.getDamageHistory().getCreatureGotBlockedThisCombat()) { - // Bushido - for (final SpellAbility ab : CardFactoryUtil.getBushidoEffects(a)) { - game.getStack().add(ab); - } - - // Rampage - final List keywords = a.getKeyword(); - final Pattern p = Pattern.compile("Rampage [0-9]+"); - Matcher m; - for (final String keyword : keywords) { - m = p.matcher(keyword); - if (m.find()) { - final String[] k = keyword.split(" "); - final int magnitude = Integer.valueOf(k[1]); - final int numBlockers = blockers.size(); - if (numBlockers > 1) { - CombatUtil.executeRampageAbility(game, a, magnitude, numBlockers); - } - } // find - } // end Rampage - } - - for (Card b : blockers) { - if (a.hasKeyword("Flanking") && !b.hasKeyword("Flanking")) { + public static void handleFlankingKeyword(final Game game, final Card attacker, final List blockers) { + for (Card blocker : blockers) { + if (attacker.hasKeyword("Flanking") && !blocker.hasKeyword("Flanking")) { int flankingMagnitude = 0; - for (String kw : a.getKeyword()) { + for (String kw : attacker.getKeyword()) { if (kw.equals("Flanking")) { flankingMagnitude++; } @@ -1045,11 +984,11 @@ public class CombatUtil { // Rule 702.23b: If a creature has multiple instances of flanking, each triggers separately. for( int i = 0; i < flankingMagnitude; i++ ) { - String effect = String.format("AB$ Pump | Cost$ 0 | Defined$ CardUID_%d | NumAtt$ -1 | NumDef$ -1 | ", b.getUniqueNumber()); - String desc = String.format("StackDescription$ Flanking (The blocking %s gets -1/-1 until end of turn)", b.getName()); + String effect = String.format("AB$ Pump | Cost$ 0 | Defined$ CardUID_%d | NumAtt$ -1 | NumDef$ -1 | ", blocker.getUniqueNumber()); + String desc = String.format("StackDescription$ Flanking (The blocking %s gets -1/-1 until end of turn)", blocker.getName()); - SpellAbility ability = AbilityFactory.getAbility(effect + desc, a); - ability.setActivatingPlayer(a.getController()); + SpellAbility ability = AbilityFactory.getAbility(effect + desc, attacker); + ability.setActivatingPlayer(attacker.getController()); ability.setDescription(ability.getStackDescription()); ability.setTrigger(true); @@ -1057,11 +996,9 @@ public class CombatUtil { } } // flanking - b.addBlockedThisTurn(a); - a.addBlockedByThisTurn(b); + blocker.addBlockedThisTurn(attacker); + attacker.addBlockedByThisTurn(blocker); } - - a.getDamageHistory().setCreatureGotBlockedThisCombat(true); } /** diff --git a/src/main/java/forge/game/event/GameEventBlockersDeclared.java b/src/main/java/forge/game/event/GameEventBlockersDeclared.java index f24c8e3d43c..13d044e1bd3 100644 --- a/src/main/java/forge/game/event/GameEventBlockersDeclared.java +++ b/src/main/java/forge/game/event/GameEventBlockersDeclared.java @@ -1,9 +1,14 @@ package forge.game.event; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Map; import forge.Card; import forge.GameEntity; +import forge.game.player.Player; +import forge.util.Lang; import forge.util.maps.MapOfLists; /** @@ -13,9 +18,11 @@ import forge.util.maps.MapOfLists; public class GameEventBlockersDeclared extends GameEvent { public final Map> blockers; + public final Player defendingPlayer; - public GameEventBlockersDeclared(Map> blockers) { + public GameEventBlockersDeclared(Player who, Map> blockers) { this.blockers = blockers; + defendingPlayer = who; } @Override @@ -24,4 +31,17 @@ public class GameEventBlockersDeclared extends GameEvent { return visitor.visit(this); } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + List blockerCards = new ArrayList(); + for(MapOfLists vv : blockers.values()) { + for(Collection cc : vv.values()) { + blockerCards.addAll(cc); + } + } + return String.format("%s declared %d blockers: %s", defendingPlayer.getName(), blockerCards.size(), Lang.joinHomogenous(blockerCards) ); + } } diff --git a/src/main/java/forge/game/phase/PhaseHandler.java b/src/main/java/forge/game/phase/PhaseHandler.java index 965f523b6b3..bd4b4c397dc 100644 --- a/src/main/java/forge/game/phase/PhaseHandler.java +++ b/src/main/java/forge/game/phase/PhaseHandler.java @@ -24,16 +24,16 @@ import java.util.Map; import java.util.Stack; import org.apache.commons.lang.time.StopWatch; -import com.google.common.base.Predicate; - import forge.Card; import forge.CardLists; import forge.FThreads; import forge.GameEntity; import forge.Singletons; import forge.CardPredicates.Presets; +import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.mana.ManaCost; +import forge.card.spellability.SpellAbility; import forge.card.staticability.StaticAbility; import forge.card.trigger.TriggerType; import forge.game.GameAge; @@ -522,66 +522,97 @@ public class PhaseHandler implements java.io.Serializable { Player whoDeclaresBlockers = playerDeclaresBlockers == null || playerDeclaresBlockers.hasLost() ? p : playerDeclaresBlockers; if ( combat.isPlayerAttacked(p) ) { whoDeclaresBlockers.getController().declareBlockers(p, combat); - } + } else + continue; if ( game.isGameOver() ) // they just like to close window at any moment return; - } while(p != playerTurn); - - combat.orderBlockersForDamageAssignment(); - combat.orderAttackersForDamageAssignment(); - - combat.removeAbsentCombatants(); - - - // Handles removing cards like Mogg Flunkies from combat if group block - // didn't occur - final List filterList = combat.getAllBlockers(); - for (Card blocker : filterList) { - final List attackers = new ArrayList(combat.getAttackersBlockedBy(blocker)); - for (Card attacker : attackers) { - boolean hasPaid = payRequiredBlockCosts(game, blocker, attacker); - - if ( !hasPaid ) { - combat.removeBlockAssignment(attacker, blocker); + + // Handles removing cards like Mogg Flunkies from combat if group block + // didn't occur + for (Card blocker : CardLists.filterControlledBy(combat.getAllBlockers(), p)) { + final List attackers = new ArrayList(combat.getAttackersBlockedBy(blocker)); + for (Card attacker : attackers) { + boolean hasPaid = payRequiredBlockCosts(game, blocker, attacker); + + if ( !hasPaid ) { + combat.removeBlockAssignment(attacker, blocker); + } } } - } - for (Card c : filterList) { - if (c.hasKeyword("CARDNAME can't attack or block alone.") && combat.isBlocking(c)) { - if (combat.getAllBlockers().size() < 2) { + + List remainingBlockers = CardLists.filterControlledBy(combat.getAllBlockers(), p); + for (Card c : remainingBlockers) { + if ( remainingBlockers.size() < 2 && c.hasKeyword("CARDNAME can't attack or block alone.") ) { combat.undoBlockingAssignment(c); } } - } - - combat.onBlockersDeclared(); - - List list = combat.getAllBlockers(); - - list = CardLists.filter(list, new Predicate() { - @Override - public boolean apply(final Card c) { - return !c.getDamageHistory().getCreatureBlockedThisCombat(); + + // Player is done declaring blockers - redraw UI at this point + + // map: defender => (many) attacker => (many) blocker + Map> blockers = new HashMap>(); + for(GameEntity ge : combat.getDefendersControlledBy(p)) { + MapOfLists protectThisDefender = new HashMapOfLists(CollectionSuppliers.arrayLists()); + for(Card att : combat.getAttackersOf(ge)) { + protectThisDefender.addAll(att, combat.getBlockers(att)); + } + blockers.put(ge, protectThisDefender); } - }); + game.fireEvent(new GameEventBlockersDeclared(p, blockers)); + } while(p != playerTurn); - CombatUtil.checkDeclareBlockers(game, list, combat); + combat.orderBlockersForDamageAssignment(); // 509.2 + combat.orderAttackersForDamageAssignment(); // 509.3 + + combat.removeAbsentCombatants(); + + combat.fireTriggersForUnblockedAttackers(); + + for (final Card c1 : combat.getAllBlockers()) { + if ( c1.getDamageHistory().getCreatureBlockedThisCombat() ) + continue; + + if (!c1.getDamageHistory().getCreatureBlockedThisCombat()) { + for (final SpellAbility ab : CardFactoryUtil.getBushidoEffects(c1)) { + game.getStack().add(ab); + } + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Blocker", c1); + runParams.put("Attacker", combat.getAttackersBlockedBy(c1).get(0)); + game.getTriggerHandler().runTrigger(TriggerType.Blocks, runParams, false); + } + + c1.getDamageHistory().setCreatureBlockedThisCombat(true); + } for (final Card a : combat.getAttackers()) { - CombatUtil.checkBlockedAttackers(game, a, combat.getBlockers(a)); - } - - // map: defender => (many) attacker => (many) blocker - Map> blockers = new HashMap>(); - for(GameEntity ge : combat.getDefenders()) { - MapOfLists protectThisDefender = new HashMapOfLists(CollectionSuppliers.arrayLists()); - for(Card att : combat.getAttackersOf(ge)) { - protectThisDefender.addAll(att, combat.getBlockers(att)); + List blockers = combat.getBlockers(a); + if ( blockers.isEmpty() ) + continue; + + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Attacker", a); + runParams.put("Blockers", blockers); + runParams.put("NumBlockers", blockers.size()); + game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false); + + if (!a.getDamageHistory().getCreatureGotBlockedThisCombat()) { + // Bushido + for (final SpellAbility ab : CardFactoryUtil.getBushidoEffects(a)) { + game.getStack().add(ab); + } + + // Rampage + CombatUtil.handleRampage(game, a, blockers); } - blockers.put(ge, protectThisDefender); + + CombatUtil.handleFlankingKeyword(game, a, blockers); + + a.getDamageHistory().setCreatureGotBlockedThisCombat(true); } - game.fireEvent(new GameEventBlockersDeclared(blockers)); } diff --git a/src/main/java/forge/sound/EventVisualizer.java b/src/main/java/forge/sound/EventVisualizer.java index d24c79a8372..24ca82be3a9 100644 --- a/src/main/java/forge/sound/EventVisualizer.java +++ b/src/main/java/forge/sound/EventVisualizer.java @@ -1,8 +1,11 @@ package forge.sound; +import java.util.Collection; + import forge.Card; import forge.Singletons; import forge.card.spellability.SpellAbility; +import forge.game.event.GameEventBlockersDeclared; import forge.game.event.GameEventCardChangeZone; import forge.game.event.GameEventCardDamaged; import forge.game.event.GameEventCardDestroyed; @@ -26,6 +29,7 @@ import forge.game.zone.ZoneType; import forge.gui.events.IUiEventVisitor; import forge.gui.events.UiEventAttackerDeclared; import forge.gui.events.UiEventBlockerAssigned; +import forge.util.maps.MapOfLists; /** * This class is in charge of converting any forge.game.event.Event to a SoundEffectType. @@ -55,7 +59,22 @@ public class EventVisualizer extends IGameEventVisitor.Base imp public SoundEffectType visit(GameEventPlayerPoisoned event) { return SoundEffectType.Poison; } public SoundEffectType visit(GameEventShuffle event) { return SoundEffectType.Shuffle; } public SoundEffectType visit(GameEventTokenCreated event) { return SoundEffectType.Token; } - + public SoundEffectType visit(GameEventBlockersDeclared event) { + boolean isLocalHuman = event.defendingPlayer.getLobbyPlayer() == Singletons.getControl().getLobby().getGuiPlayer(); + if (isLocalHuman) + return null; // already played sounds in interactive mode + + for(MapOfLists ab : event.blockers.values()) { + for(Collection bb : ab.values()) { + if ( !bb.isEmpty() ) { + // hasAnyBlocker = true; + return SoundEffectType.Block; + } + } + } + return null; + } + /** * Plays the sound corresponding to the outcome of the duel. */