mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 04:38:00 +00:00
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
This commit is contained in:
@@ -81,6 +81,16 @@ public class Combat {
|
||||
return Lists.newArrayList(attackedEntities.keySet());
|
||||
}
|
||||
|
||||
public final List<GameEntity> getDefendersControlledBy(Player who) {
|
||||
List<GameEntity> 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<Card> getDefendingPlaneswalkers() {
|
||||
final List<Card> pwDefending = new ArrayList<Card>();
|
||||
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<AttackingBand> abs : attackedEntities.values()) {
|
||||
for(AttackingBand ab : abs) {
|
||||
Collection<Card> blockers = blockedBands.get(ab);
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* checkDeclareBlockers.
|
||||
* </p>
|
||||
* @param game
|
||||
*
|
||||
* @param cl
|
||||
* a {@link forge.CardList} object.
|
||||
*/
|
||||
public static void checkDeclareBlockers(Game game, final List<Card> 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<String, Object> runParams = new HashMap<String, Object>();
|
||||
runParams.put("Blocker", c);
|
||||
final Card attacker = combat.getAttackersBlockedBy(c).get(0);
|
||||
runParams.put("Attacker", attacker);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.Blocks, runParams, false);
|
||||
}
|
||||
|
||||
c.getDamageHistory().setCreatureBlockedThisCombat(true);
|
||||
} // for
|
||||
public static void handleRampage(final Game game, final Card a, final List<Card> blockers) {
|
||||
for (final String keyword : a.getKeyword()) {
|
||||
int idx = keyword.indexOf("Rampage ");
|
||||
if ( idx < 0)
|
||||
continue;
|
||||
|
||||
} // checkDeclareBlockers
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* checkBlockedAttackers.
|
||||
* </p>
|
||||
* @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<Card> blockers) {
|
||||
if (blockers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run triggers
|
||||
final HashMap<String, Object> runParams = new HashMap<String, Object>();
|
||||
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<String> 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) {
|
||||
final int magnitude = Integer.valueOf(keyword.substring(idx + "Rampage ".length()));
|
||||
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<Card> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<GameEntity, MapOfLists<Card, Card>> blockers;
|
||||
public final Player defendingPlayer;
|
||||
|
||||
public GameEventBlockersDeclared(Map<GameEntity, MapOfLists<Card, Card>> blockers) {
|
||||
public GameEventBlockersDeclared(Player who, Map<GameEntity, MapOfLists<Card, Card>> 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<Card> blockerCards = new ArrayList<Card>();
|
||||
for(MapOfLists<Card, Card> vv : blockers.values()) {
|
||||
for(Collection<Card> cc : vv.values()) {
|
||||
blockerCards.addAll(cc);
|
||||
}
|
||||
}
|
||||
return String.format("%s declared %d blockers: %s", defendingPlayer.getName(), blockerCards.size(), Lang.joinHomogenous(blockerCards) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,22 +522,15 @@ 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<Card> filterList = combat.getAllBlockers();
|
||||
for (Card blocker : filterList) {
|
||||
for (Card blocker : CardLists.filterControlledBy(combat.getAllBlockers(), p)) {
|
||||
final List<Card> attackers = new ArrayList<Card>(combat.getAttackersBlockedBy(blocker));
|
||||
for (Card attacker : attackers) {
|
||||
boolean hasPaid = payRequiredBlockCosts(game, blocker, attacker);
|
||||
@@ -547,41 +540,79 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Card c : filterList) {
|
||||
if (c.hasKeyword("CARDNAME can't attack or block alone.") && combat.isBlocking(c)) {
|
||||
if (combat.getAllBlockers().size() < 2) {
|
||||
|
||||
List<Card> 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<Card> list = combat.getAllBlockers();
|
||||
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return !c.getDamageHistory().getCreatureBlockedThisCombat();
|
||||
}
|
||||
});
|
||||
|
||||
CombatUtil.checkDeclareBlockers(game, list, combat);
|
||||
|
||||
for (final Card a : combat.getAttackers()) {
|
||||
CombatUtil.checkBlockedAttackers(game, a, combat.getBlockers(a));
|
||||
}
|
||||
// Player is done declaring blockers - redraw UI at this point
|
||||
|
||||
// map: defender => (many) attacker => (many) blocker
|
||||
Map<GameEntity, MapOfLists<Card, Card>> blockers = new HashMap<GameEntity, MapOfLists<Card,Card>>();
|
||||
for(GameEntity ge : combat.getDefenders()) {
|
||||
for(GameEntity ge : combat.getDefendersControlledBy(p)) {
|
||||
MapOfLists<Card, Card> protectThisDefender = new HashMapOfLists<Card, Card>(CollectionSuppliers.<Card>arrayLists());
|
||||
for(Card att : combat.getAttackersOf(ge)) {
|
||||
protectThisDefender.addAll(att, combat.getBlockers(att));
|
||||
}
|
||||
blockers.put(ge, protectThisDefender);
|
||||
}
|
||||
game.fireEvent(new GameEventBlockersDeclared(blockers));
|
||||
game.fireEvent(new GameEventBlockersDeclared(p, blockers));
|
||||
} while(p != playerTurn);
|
||||
|
||||
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<String, Object> runParams = new HashMap<String, Object>();
|
||||
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()) {
|
||||
List<Card> blockers = combat.getBlockers(a);
|
||||
if ( blockers.isEmpty() )
|
||||
continue;
|
||||
|
||||
// Run triggers
|
||||
final HashMap<String, Object> runParams = new HashMap<String, Object>();
|
||||
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);
|
||||
}
|
||||
|
||||
CombatUtil.handleFlankingKeyword(game, a, blockers);
|
||||
|
||||
a.getDamageHistory().setCreatureGotBlockedThisCombat(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,6 +59,21 @@ public class EventVisualizer extends IGameEventVisitor.Base<SoundEffectType> 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<Card, Card> ab : event.blockers.values()) {
|
||||
for(Collection<Card> bb : ab.values()) {
|
||||
if ( !bb.isEmpty() ) {
|
||||
// hasAnyBlocker = true;
|
||||
return SoundEffectType.Block;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the sound corresponding to the outcome of the duel.
|
||||
|
||||
Reference in New Issue
Block a user