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

View File

@@ -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;
}

View File

@@ -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<GameLogEntry> {
private final GameLog log;
@@ -168,40 +171,42 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
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<Card> attackers = combat.getAttackersOf(defender);
for (Entry<GameEntity, Collection<Card>> kv : ev.attackersMap.entrySet()) {
Collection<Card> 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<Card> blockers = null;
Collection<Card> blockers = null;
for (GameEntity defender : combat.getDefenders()) {
List<Card> attackers = combat.getAttackersOf(defender);
for (Entry<GameEntity, MapOfLists<Card, Card>> kv : ev.blockers.entrySet()) {
GameEntity defender = kv.getKey();
MapOfLists<Card, Card> attackers = kv.getValue();
if (attackers == null || attackers.isEmpty()) {
continue;
}
@@ -209,17 +214,17 @@ public class GameLogFormatter extends IGameEventVisitor.Base<GameLogEntry> {
String controllerName = defender instanceof Card ? ((Card)defender).getController().getName() : defender.getName();
boolean firstAttacker = true;
for (final Card attacker : attackers) {
for (final Entry<Card, Collection<Card>> 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;
}
}

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> {
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<T> {
// This is base class for all visitors.
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(GameEventCardDamaged 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(GameEventPlayerDamaged event) { return null; }
}
}

View File

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

View File

@@ -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<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) {
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<String, Object> runParams = new HashMap<String, Object>();
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<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;
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;

View File

@@ -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<PhaseType> 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
)
);

View File

@@ -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<Card> attackers = new ArrayList<Card>(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<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);
}
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<Card> list = new ArrayList<Card>();
list.addAll(combat.getAllBlockers());
List<Card> list = combat.getAllBlockers();
list = CardLists.filter(list, new Predicate<Card>() {
@Override
@@ -128,16 +108,30 @@ public class PhaseUtil {
}
});
final List<Card> 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<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;
import java.util.Collection;
import java.util.List;
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(List<T> objects, Function<T, String> accessor) {
public static <T> String joinHomogenous(Collection<T> objects) { return joinHomogenous(objects, null); }
public static <T> String joinHomogenous(Collection<T> objects, Function<T, String> accessor) {
int remaining = objects.size();
StringBuilder sb = new StringBuilder();
for(T obj : objects) {