From 74059bda82788eed5973d6235fdc91b22e2234bf Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 24 Jun 2013 09:33:19 +0000 Subject: [PATCH] Combat instance lifespan limited to Combat phase (for the rest combat = null, checks will return 'not attacking', 'not blocking'), the very object is stored in PhaseHandler Card: removed methods to test if card is attacking/blocking, because these properties are related to combat, not the card itself. AiAttackController - no longer creates Combat. Instead it uses a provided instance and fills attackers there ComputerUtilBlock.java became non-static class AiBlockController, also modifies the provided Combat instance --- .gitattributes | 2 +- src/main/java/forge/Card.java | 210 ++---- .../java/forge/card/ability/AbilityUtils.java | 2 +- .../java/forge/card/ability/ai/AnimateAi.java | 4 +- .../java/forge/card/ability/ai/AttachAi.java | 41 +- .../forge/card/ability/ai/ChangeZoneAi.java | 27 +- .../forge/card/ability/ai/ChooseCardAi.java | 23 +- .../forge/card/ability/ai/ChooseSourceAi.java | 6 +- .../java/forge/card/ability/ai/CloneAi.java | 4 +- .../card/ability/ai/DamagePreventAi.java | 21 +- .../java/forge/card/ability/ai/DebuffAi.java | 52 +- .../java/forge/card/ability/ai/EncodeAi.java | 42 +- .../forge/card/ability/ai/LifeGainAi.java | 3 +- .../java/forge/card/ability/ai/ProtectAi.java | 27 +- .../forge/card/ability/ai/PumpAiBase.java | 98 ++- .../java/forge/card/ability/ai/PumpAllAi.java | 11 +- .../forge/card/ability/ai/RegenerateAi.java | 9 +- .../card/ability/ai/RegenerateAllAi.java | 5 +- .../ability/effects/BecomesBlockedEffect.java | 2 +- .../ability/effects/ChangeZoneEffect.java | 12 +- .../ability/effects/ChooseColorEffect.java | 2 +- .../ability/effects/ChooseSourceEffect.java | 8 +- .../ability/effects/CopyPermanentEffect.java | 5 +- .../card/ability/effects/EndTurnEffect.java | 2 +- .../effects/RemoveFromCombatEffect.java | 5 +- .../card/ability/effects/TokenEffect.java | 13 +- src/main/java/forge/game/Game.java | 13 +- src/main/java/forge/game/GameAction.java | 5 +- .../forge/game/ai/AiAttackController.java | 47 +- ...rUtilBlock.java => AiBlockController.java} | 514 ++++--------- src/main/java/forge/game/ai/AiController.java | 21 +- src/main/java/forge/game/ai/ComputerUtil.java | 22 +- .../java/forge/game/ai/ComputerUtilCard.java | 8 +- .../forge/game/ai/ComputerUtilCombat.java | 25 +- .../java/forge/game/combat/AttackingBand.java | 64 +- src/main/java/forge/game/phase/Combat.java | 703 +++++++----------- .../java/forge/game/phase/CombatUtil.java | 108 ++- .../java/forge/game/phase/PhaseHandler.java | 165 ++-- .../forge/game/player/PlayerController.java | 5 +- .../forge/game/player/PlayerControllerAi.java | 15 +- .../game/player/PlayerControllerHuman.java | 12 +- src/main/java/forge/gui/GuiDisplayUtil.java | 2 +- .../java/forge/gui/input/InputAttack.java | 4 +- src/main/java/forge/gui/input/InputBlock.java | 68 +- src/main/java/forge/gui/match/CMatchUI.java | 2 +- .../forge/gui/match/TargetingOverlay.java | 6 +- .../forge/gui/match/controllers/CCombat.java | 25 +- .../forge/gui/match/controllers/CDock.java | 13 +- .../java/forge/sound/EventVisualizer.java | 3 - .../java/forge/util/maps/EnumMapOfLists.java | 2 +- .../java/forge/util/maps/HashMapOfLists.java | 2 +- src/main/java/forge/util/maps/MapOfLists.java | 1 + .../java/forge/util/maps/TreeMapOfLists.java | 2 +- .../java/forge/view/arcane/CardPanel.java | 11 +- 54 files changed, 963 insertions(+), 1541 deletions(-) rename src/main/java/forge/game/ai/{ComputerUtilBlock.java => AiBlockController.java} (61%) diff --git a/.gitattributes b/.gitattributes index e5f25d1405f..e0fecb7b088 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14465,11 +14465,11 @@ src/main/java/forge/game/Match.java -text src/main/java/forge/game/PlanarDice.java -text src/main/java/forge/game/RegisteredPlayer.java -text src/main/java/forge/game/ai/AiAttackController.java svneol=native#text/plain +src/main/java/forge/game/ai/AiBlockController.java svneol=native#text/plain src/main/java/forge/game/ai/AiController.java svneol=native#text/plain src/main/java/forge/game/ai/AiProfileUtil.java -text src/main/java/forge/game/ai/AiProps.java -text src/main/java/forge/game/ai/ComputerUtil.java svneol=native#text/plain -src/main/java/forge/game/ai/ComputerUtilBlock.java svneol=native#text/plain src/main/java/forge/game/ai/ComputerUtilCard.java -text src/main/java/forge/game/ai/ComputerUtilCombat.java -text src/main/java/forge/game/ai/ComputerUtilCost.java -text diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index 067201f8841..6162e759e57 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -5357,6 +5357,8 @@ public class Card extends GameEntity implements Comparable { */ @Override public boolean hasProperty(final String property, final Player sourceController, final Card source) { + final Game game = getGame(); + final Combat combat = game.getCombat(); // by name can also have color names, so needs to happen before colors. if (property.startsWith("named")) { if (!this.getName().equals(property.substring(5))) { @@ -5438,10 +5440,10 @@ public class Card extends GameEntity implements Comparable { return false; } } else if (property.startsWith("DefenderCtrl")) { - if (!getGame().getPhaseHandler().inCombat()) { + if (!game.getPhaseHandler().inCombat()) { return false; } - if (!getGame().getCombat().getDefendingPlayerRelatedTo(source).contains(this.getController())) { + if (getGame().getCombat().getDefendingPlayerRelatedTo(source) != this.getController()) { return false; } } else if (property.startsWith("EnchantedPlayerCtrl")) { @@ -5455,7 +5457,7 @@ public class Card extends GameEntity implements Comparable { } } else if (property.startsWith("RememberedPlayerCtrl")) { if (source.getRemembered().isEmpty()) { - final Card newCard = getGame().getCardState(source); + final Card newCard = game.getCardState(source); for (final Object o : newCard.getRemembered()) { if (o instanceof Player) { if (!this.getController().equals(o)) { @@ -5501,11 +5503,11 @@ public class Card extends GameEntity implements Comparable { } } } else if (property.startsWith("ActivePlayerCtrl")) { - if (!getGame().getPhaseHandler().isPlayerTurn(this.getController())) { + if (!game.getPhaseHandler().isPlayerTurn(this.getController())) { return false; } } else if (property.startsWith("NonActivePlayerCtrl")) { - if (getGame().getPhaseHandler().isPlayerTurn(this.getController())) { + if (game.getPhaseHandler().isPlayerTurn(this.getController())) { return false; } } else if (property.startsWith("YouOwn")) { @@ -5845,11 +5847,11 @@ public class Card extends GameEntity implements Comparable { return false; } } else if (restriction.equals("MostProminentColor")) { - byte mask = CardFactoryUtil.getMostProminentColors(getGame().getCardsIn(ZoneType.Battlefield)); + byte mask = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield)); if( !CardUtil.getColors(this).hasAnyColor(mask)) return false; } else if (restriction.equals("LastCastThisTurn")) { - final List c = source.getGame().getStack().getCardsCastThisTurn(); + final List c = game.getStack().getCardsCastThisTurn(); if (c.isEmpty() || !this.sharesColorWith(c.get(c.size() - 1))) { return false; } @@ -5872,7 +5874,7 @@ public class Card extends GameEntity implements Comparable { } String color = props[1]; - byte mostProm = CardFactoryUtil.getMostProminentColors(getGame().getCardsIn(ZoneType.Battlefield)); + byte mostProm = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield)); return ColorSet.fromMask(mostProm).hasAnyColor(MagicColor.fromName(color)); } else if (property.startsWith("notSharesColorWith")) { if (property.equals("notSharesColorWith")) { @@ -5971,7 +5973,7 @@ public class Card extends GameEntity implements Comparable { return false; } else if (restriction.equals("EachTopLibrary")) { final List list = new ArrayList(); - for (Player p : getGame().getPlayers()) { + for (Player p : game.getPlayers()) { final Card top = p.getCardsIn(ZoneType.Library).get(0); list.add(top); } @@ -5998,14 +6000,14 @@ public class Card extends GameEntity implements Comparable { } return false; } else if (restriction.equals(ZoneType.Graveyard.toString())) { - for (final Card card : getGame().getCardsIn(ZoneType.Graveyard)) { + for (final Card card : game.getCardsIn(ZoneType.Graveyard)) { if (this.getName().equals(card.getName())) { return true; } } return false; } else if (restriction.equals(ZoneType.Battlefield.toString())) { - for (final Card card : getGame().getCardsIn(ZoneType.Battlefield)) { + for (final Card card : game.getCardsIn(ZoneType.Battlefield)) { if (this.getName().equals(card.getName())) { return true; } @@ -6050,7 +6052,7 @@ public class Card extends GameEntity implements Comparable { } return false; } else if (restriction.equals("NonToken")) { - final List list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), + final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.NON_TOKEN); for (final Card card : list) { if (this.getName().equals(card.getName())) { @@ -6208,11 +6210,11 @@ public class Card extends GameEntity implements Comparable { return false; } } else if (property.startsWith("enteredBattlefieldThisTurn")) { - if (!(this.getTurnInZone() == getGame().getPhaseHandler().getTurn())) { + if (!(this.getTurnInZone() == game.getPhaseHandler().getTurn())) { return false; } } else if (property.startsWith("notEnteredBattlefieldThisTurn")) { - if (this.getTurnInZone() == getGame().getPhaseHandler().getTurn()) { + if (this.getTurnInZone() == game.getPhaseHandler().getTurn()) { return false; } } else if (property.startsWith("firstTurnControlled")) { @@ -6288,7 +6290,7 @@ public class Card extends GameEntity implements Comparable { return false; } } else if (property.startsWith("greatestPower")) { - final List list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); for (final Card crd : list) { if (crd.getNetAttack() > this.getNetAttack()) { return false; @@ -6302,21 +6304,21 @@ public class Card extends GameEntity implements Comparable { } } } else if (property.startsWith("leastPower")) { - final List list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); for (final Card crd : list) { if (crd.getNetAttack() < this.getNetAttack()) { return false; } } } else if (property.startsWith("leastToughness")) { - final List list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); for (final Card crd : list) { if (crd.getNetDefense() < this.getNetDefense()) { return false; } } } else if (property.startsWith("greatestCMC")) { - final List list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES); + final List list = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); for (final Card crd : list) { if (crd.isSplitCard()) { if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) > this.getCMC() || crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > this.getCMC()) { @@ -6332,7 +6334,7 @@ public class Card extends GameEntity implements Comparable { List list = new ArrayList(); for (final Object o : source.getRemembered()) { if (o instanceof Card) { - list.add(getGame().getCardState((Card) o)); + list.add(game.getCardState((Card) o)); } } if (!list.contains(this)) { @@ -6343,7 +6345,7 @@ public class Card extends GameEntity implements Comparable { return false; } } else if (property.startsWith("lowestCMC")) { - final List list = getGame().getCardsIn(ZoneType.Battlefield); + final List list = game.getCardsIn(ZoneType.Battlefield); for (final Card crd : list) { if (!crd.isLand() && !crd.isImmutable()) { if (crd.isSplitCard()) { @@ -6395,7 +6397,7 @@ public class Card extends GameEntity implements Comparable { return false; } } else if (property.startsWith("suspended")) { - if (!this.hasSuspend() || !getGame().isCardExiled(this) + if (!this.hasSuspend() || !game.isCardExiled(this) || !(this.getCounters(CounterType.getType("TIME")) >= 1)) { return false; } @@ -6478,60 +6480,54 @@ public class Card extends GameEntity implements Comparable { if (!Expressions.compare(actualnumber, comparator, number)) { return false; } - } else if (property.startsWith("attacking")) { - if (property.equals("attacking")) { - if (!this.isAttacking()) { - return false; - } - } else if (property.equals("attackingYou")) { - if (!this.isAttacking(sourceController)) { - return false; - } - } + } + // These predicated refer to ongoing combat. If no combat happens, they'll return false (meaning not attacking/blocking ATM) + else if (property.startsWith("attacking")) { + if ( null == combat ) return false; + if (property.equals("attacking")) return combat.isAttacking(this); + if (property.equals("attackingYou")) return combat.isAttacking(this, sourceController); } else if (property.startsWith("notattacking")) { - if (this.isAttacking()) { - return false; - } + return null == combat || !combat.isAttacking(this); } else if (property.equals("attackedBySourceThisCombat")) { - final GameEntity defender = getGame().getCombat().getDefenderByAttacker(source); + final GameEntity defender = game.getCombat().getDefenderByAttacker(source); if (defender instanceof Card) { if (!this.equals((Card) defender)) { return false; } } - } else if (property.equals("blocking")) { - if (!this.isBlocking()) { + } else if (property.startsWith("blocking")) { + if ( null == combat ) return false; + String what = property.substring("blocking".length()); + + if( StringUtils.isEmpty(what)) return combat.isBlocking(this); + if (what.startsWith("Source")) return combat.isBlocking(this, source) ; + if (what.startsWith("CreatureYouCtrl")) { + for (final Card c : CardLists.filter(sourceController.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) + if (combat.isBlocking(this, c)) + return true; return false; } - } else if (property.startsWith("blockingSource")) { - if (!this.isBlocking(source)) { - return false; - } - } else if (property.startsWith("blockingCreatureYouCtrl")) { - final List list = CardLists.filter(sourceController.getCardsIn(ZoneType.Battlefield), Presets.CREATURES); - for (final Card c : list) { - if (this.isBlocking(c)) { - return true; - } - return false; - } - } else if (property.startsWith("blockingRemembered")) { - for (final Object o : source.getRemembered()) { - if (o instanceof Card) { - final Card card = (Card) o; - if (this.isBlocking(card)) { + if (what.startsWith("Remembered")) { + for (final Object o : source.getRemembered()) { + if (o instanceof Card && combat.isBlocking(this, (Card) o)) { return true; } } + return false; } - return false; + } else if (property.startsWith("notblocking")) { + return null == combat || !combat.isBlocking(this); + } + // Nex predicates refer to past combat and don't need a reference to actual combat + else if (property.equals("blocked")) { + return null != combat && combat.isBlocked(this); + } else if (property.startsWith("blockedBySource")) { + return null != combat && combat.isBlocking(source, this); } else if (property.startsWith("isBlockedByRemembered")) { + if ( null == combat ) return false; for (final Object o : source.getRemembered()) { - if (o instanceof Card) { - final Card crd = (Card) o; - if (this.isBlockedBy(crd)) { - return true; - } + if (o instanceof Card && combat.isBlocking((Card) o, this)) { + return true; } } return false; @@ -6555,20 +6551,8 @@ public class Card extends GameEntity implements Comparable { } } } - } else if (property.startsWith("notblocking")) { - if (this.isBlocking()) { - return false; - } - } else if (property.equals("blocked")) { - if (!this.isBlocked()) { - return false; - } - } else if (property.startsWith("blockedBySource")) { - if (!this.isBlockedBy(source)) { - return false; - } } else if (property.startsWith("unblocked")) { - if (!getGame().getCombat().isUnblocked(this)) { + if (!game.getCombat().isUnblocked(this)) { return false; } } else if (property.equals("attackersBandedWith")) { @@ -6576,9 +6560,7 @@ public class Card extends GameEntity implements Comparable { // You don't band with yourself return false; } - - Combat combat = getGame().getCombat(); - AttackingBand band = combat.getBandByAttacker(source); + AttackingBand band = combat == null ? null : combat.getBandOfAttacker(source); if (band == null || !band.getAttackers().contains(this)) { return false; } @@ -6889,82 +6871,6 @@ public class Card extends GameEntity implements Comparable { this.usedToPayCost = b; } - /** - *

- * isAttacking. - *

- * - * @return a boolean. - */ - public final boolean isAttacking() { - return getGame().getCombat().isAttacking(this); - } - - /** - *

- * isAttacking. - *

- * @param ge the GameEntity to check - * @return a boolean. - */ - public final boolean isAttacking(GameEntity ge) { - Combat combat = getGame().getCombat(); - GameEntity defender = combat.getDefenderByAttacker(this); - if (!combat.isAttacking(this) || defender == null) { - return false; - } - return defender.equals(ge); - } - - /** - *

- * isBlocking. - *

- * - * @return a boolean. - */ - public final boolean isBlocking() { - final List blockers = getGame().getCombat().getAllBlockers(); - return blockers.contains(this); - } - - /** - *

- * isBlocked. - *

- * - * @return a boolean. - */ - public final boolean isBlocked() { - return getGame().getCombat().isBlocked(this); - } - - /** - *

- * isBlocking. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @return a boolean. - */ - public final boolean isBlocking(final Card attacker) { - return getGame().getCombat().getAttackersBlockedBy(this).contains(attacker); - } - - /** - *

- * isBlockedBy. - *

- * - * @param blocker - * a {@link forge.Card} object. - * @return a boolean. - */ - public final boolean isBlockedBy(final Card blocker) { - return getGame().getCombat().getAttackersBlockedBy(blocker).contains(this); - } - // ///////////////////////// // // Damage code diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index 01094aed568..97829948e05 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -896,7 +896,7 @@ public class AbilityUtils { players.add(p); } } else if (defined.equals("DefendingPlayer")) { - players.addAll(game.getCombat().getDefendingPlayerRelatedTo(card)); + players.add(game.getCombat().getDefendingPlayerRelatedTo(card)); } else if (defined.equals("ChosenPlayer")) { final Player p = card.getChosenPlayer(); if (!players.contains(p)) { diff --git a/src/main/java/forge/card/ability/ai/AnimateAi.java b/src/main/java/forge/card/ability/ai/AnimateAi.java index ce31802b926..d1a91e12730 100644 --- a/src/main/java/forge/card/ability/ai/AnimateAi.java +++ b/src/main/java/forge/card/ability/ai/AnimateAi.java @@ -60,9 +60,7 @@ public class AnimateAi extends SpellAbilityAi { // don't use instant speed animate abilities outside humans // Combat_Declare_Attackers_InstantAbility step - if ((!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS) - || (game.getCombat().getAttackers().isEmpty())) - && game.getPhaseHandler().isPlayerTurn(opponent)) { + if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_ATTACKERS, opponent) || game.getCombat().getAttackers().isEmpty()) { return false; } diff --git a/src/main/java/forge/card/ability/ai/AttachAi.java b/src/main/java/forge/card/ability/ai/AttachAi.java index f6883ea6f31..cd94ce63219 100644 --- a/src/main/java/forge/card/ability/ai/AttachAi.java +++ b/src/main/java/forge/card/ability/ai/AttachAi.java @@ -817,7 +817,12 @@ public class AttachAi extends SpellAbilityAi { prefList = CardLists.filter(prefList, new Predicate() { @Override public boolean apply(final Card c) { - return containsUsefulKeyword(keywords, c, sa, pow); + for (final String keyword : keywords) { + if (isUsefulAttachKeyword(keyword, c, sa, pow)) { + return true; + } + } + return false; } }); } @@ -991,25 +996,6 @@ public class AttachAi extends SpellAbilityAi { return c; } - /** - * Contains useful keyword. - * - * @param keywords - * the keywords - * @param card - * the card - * @param sa SpellAbility - * @return true, if successful - */ - private static boolean containsUsefulKeyword(final ArrayList keywords, final Card card, final SpellAbility sa, final int powerBonus) { - for (final String keyword : keywords) { - if (isUsefulAttachKeyword(keyword, card, sa, powerBonus)) { - return true; - } - } - return false; - } - /** * Contains useful curse keyword. * @@ -1040,7 +1026,10 @@ public class AttachAi extends SpellAbilityAi { * @return true, if is useful keyword */ private static boolean isUsefulAttachKeyword(final String keyword, final Card card, final SpellAbility sa, final int powerBonus) { - final PhaseHandler ph = sa.getActivatingPlayer().getGame().getPhaseHandler(); + final Player ai = sa.getActivatingPlayer(); + final Player opponent = ai.getOpponent(); + final PhaseHandler ph = ai.getGame().getPhaseHandler(); + if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) { return false; } @@ -1053,7 +1042,7 @@ public class AttachAi extends SpellAbilityAi { if (evasive) { if (card.getNetCombatDamage() + powerBonus <= 0 || !CombatUtil.canAttackNextTurn(card) - || !CombatUtil.canBeBlocked(card)) { + || !CombatUtil.canBeBlocked(card, opponent)) { return false; } } else if (keyword.equals("Haste")) { @@ -1068,7 +1057,7 @@ public class AttachAi extends SpellAbilityAi { return true; } else if (keyword.endsWith("Deathtouch") || keyword.endsWith("Wither")) { if (card.getNetCombatDamage() + powerBonus <= 0 - || ((!CombatUtil.canBeBlocked(card) || !CombatUtil.canAttackNextTurn(card)) + || ((!CombatUtil.canBeBlocked(card, opponent) || !CombatUtil.canAttackNextTurn(card)) && !CombatUtil.canBlock(card, true))) { return false; } @@ -1084,17 +1073,17 @@ public class AttachAi extends SpellAbilityAi { } else if (keyword.startsWith("Flanking")) { if (card.getNetCombatDamage() + powerBonus <= 0 || !CombatUtil.canAttackNextTurn(card) - || !CombatUtil.canBeBlocked(card)) { + || !CombatUtil.canBeBlocked(card, opponent)) { return false; } } else if (keyword.startsWith("Bushido")) { - if ((!CombatUtil.canBeBlocked(card) || !CombatUtil.canAttackNextTurn(card)) + if ((!CombatUtil.canBeBlocked(card, opponent) || !CombatUtil.canAttackNextTurn(card)) && !CombatUtil.canBlock(card, true)) { return false; } } else if (keyword.equals("Trample")) { if (card.getNetCombatDamage() + powerBonus <= 1 - || !CombatUtil.canBeBlocked(card) + || !CombatUtil.canBeBlocked(card, opponent) || !CombatUtil.canAttackNextTurn(card)) { return false; } diff --git a/src/main/java/forge/card/ability/ai/ChangeZoneAi.java b/src/main/java/forge/card/ability/ai/ChangeZoneAi.java index 98ff7e64aa5..c553551c479 100644 --- a/src/main/java/forge/card/ability/ai/ChangeZoneAi.java +++ b/src/main/java/forge/card/ability/ai/ChangeZoneAi.java @@ -32,7 +32,7 @@ import forge.card.trigger.TriggerType; import forge.game.Game; import forge.game.GlobalRuleChange; import forge.game.ai.ComputerUtil; -import forge.game.ai.ComputerUtilBlock; +import forge.game.ai.AiBlockController; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; @@ -500,13 +500,12 @@ public class ChangeZoneAi extends SpellAbilityAi { */ private static Card chooseCreature(final Player ai, List list) { // Creating a new combat for testing purposes. - Combat combat = new Combat(); - combat.initiatePossibleDefenders(ai); - List attackers = ai.getOpponent().getCreaturesInPlay(); - for (Card att : attackers) { + Combat combat = new Combat(ai.getOpponent()); + for (Card att : ai.getOpponent().getCreaturesInPlay()) { combat.addAttacker(att, ai); } - combat = ComputerUtilBlock.getBlockers(ai, combat, ai.getCreaturesInPlay()); + AiBlockController block = new AiBlockController(ai); + block.assignBlockers(combat); if (ComputerUtilCombat.lifeInDanger(ai, combat)) { // need something AI can cast now @@ -523,10 +522,8 @@ public class ChangeZoneAi extends SpellAbilityAi { } // ************************************************************************************* - // **************** Known Origin (Battlefield/Graveyard/Exile) - // ************************* - // ******* Known origin cards are chosen during casting of the spell - // (target) ********** + // **************** Known Origin (Battlefield/Graveyard/Exile) ************************* + // ******* Known origin cards are chosen during casting of the spell (target) ********** // ************************************************************************************* /** @@ -690,6 +687,7 @@ public class ChangeZoneAi extends SpellAbilityAi { final ZoneType origin = ZoneType.listValueOf(sa.getParam("Origin")).get(0); final ZoneType destination = ZoneType.smartValueOf(sa.getParam("Destination")); final TargetRestrictions tgt = sa.getTargetRestrictions(); + final Game game = ai.getGame(); final AbilitySub abSub = sa.getSubAbility(); ApiType subApi = null; @@ -702,7 +700,7 @@ public class ChangeZoneAi extends SpellAbilityAi { } sa.resetTargets(); - List list = CardLists.getValidCards(ai.getGame().getCardsIn(origin), tgt.getValidTgts(), ai, source); + List list = CardLists.getValidCards(game.getCardsIn(origin), tgt.getValidTgts(), ai, source); list = CardLists.getTargetableCards(list, sa); if (sa.hasParam("AITgts")) { list = CardLists.getValidCards(list, sa.getParam("AITgts"), sa.getActivatingPlayer(), source); @@ -730,7 +728,7 @@ public class ChangeZoneAi extends SpellAbilityAi { // check stack for something on the stack that will kill // anything i control - if (!ai.getGame().getStack().isEmpty()) { + if (!game.getStack().isEmpty()) { final List objects = ComputerUtil.predictThreatenedObjects(ai, sa); final List threatenedTargets = new ArrayList(); @@ -748,12 +746,13 @@ public class ChangeZoneAi extends SpellAbilityAi { } } // Save combatants - else if (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + Combat combat = game.getCombat(); final List combatants = CardLists.filter(aiPermanents, CardPredicates.Presets.CREATURES); CardLists.sortByEvaluateCreature(combatants); for (final Card c : combatants) { - if (c.getShield() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c) && c.getOwner() == ai && !c.isToken()) { + if (c.getShield() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && c.getOwner() == ai && !c.isToken()) { sa.getTargets().add(c); return true; } diff --git a/src/main/java/forge/card/ability/ai/ChooseCardAi.java b/src/main/java/forge/card/ability/ai/ChooseCardAi.java index db86b89e5b4..a367bb7989a 100644 --- a/src/main/java/forge/card/ability/ai/ChooseCardAi.java +++ b/src/main/java/forge/card/ability/ai/ChooseCardAi.java @@ -13,6 +13,7 @@ import forge.card.spellability.TargetRestrictions; import forge.game.Game; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; +import forge.game.phase.Combat; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -64,19 +65,18 @@ public class ChooseCardAi extends SpellAbilityAi { } else if (sa.getParam("AILogic").equals("Never")) { return false; } else if (sa.getParam("AILogic").equals("NeedsPrevention")) { - if (!game.getPhaseHandler().getPhase() .equals(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; } + final Combat combat = game.getCombat(); choices = CardLists.filter(choices, new Predicate() { @Override public boolean apply(final Card c) { - if (!c.isAttacking(ai) || !game.getCombat().isUnblocked(c)) { + if (!combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { return false; } - if (host.getName().equals("Forcefield")) { - return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 1; - } - return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 0; + int ref = host.getName().equals("Forcefield") ? 1 : 0; + return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref; } }); if (choices.isEmpty()) { @@ -122,17 +122,16 @@ public class ChooseCardAi extends SpellAbilityAi { } choice = ComputerUtilCard.getBestAI(options); } else if (logic.equals("NeedsPrevention")) { + final Game game = ai.getGame(); + final Combat combat = game.getCombat(); List better = CardLists.filter(options, new Predicate() { @Override public boolean apply(final Card c) { - final Game game = ai.getGame(); - if (!c.isAttacking(ai) || !game.getCombat().isUnblocked(c)) { + if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { return false; } - if (host.getName().equals("Forcefield")) { - return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 1; - } - return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 0; + int ref = host.getName().equals("Forcefield") ? 1 : 0; + return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > ref; } }); if (!better.isEmpty()) { diff --git a/src/main/java/forge/card/ability/ai/ChooseSourceAi.java b/src/main/java/forge/card/ability/ai/ChooseSourceAi.java index 53b855912e0..a1219a3fa53 100644 --- a/src/main/java/forge/card/ability/ai/ChooseSourceAi.java +++ b/src/main/java/forge/card/ability/ai/ChooseSourceAi.java @@ -16,6 +16,7 @@ import forge.card.spellability.TargetRestrictions; import forge.game.Game; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; +import forge.game.phase.Combat; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -100,13 +101,14 @@ public class ChooseSourceAi extends SpellAbilityAi { if (sa.hasParam("Choices")) { choices = CardLists.getValidCards(choices, sa.getParam("Choices"), host.getController(), host); } + final Combat combat = game.getCombat(); choices = CardLists.filter(choices, new Predicate() { @Override public boolean apply(final Card c) { - if (!c.isAttacking(ai) || !game.getCombat().isUnblocked(c)) { + if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { return false; } - return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 0; + return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0; } }); if (choices.isEmpty()) { diff --git a/src/main/java/forge/card/ability/ai/CloneAi.java b/src/main/java/forge/card/ability/ai/CloneAi.java index 81d3782d133..421ad47804d 100644 --- a/src/main/java/forge/card/ability/ai/CloneAi.java +++ b/src/main/java/forge/card/ability/ai/CloneAi.java @@ -46,9 +46,7 @@ public class CloneAi extends SpellAbilityAi { // don't use instant speed clone abilities outside humans // Combat_Declare_Attackers_InstantAbility step - if ((!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) - || game.getCombat().getAttackers().isEmpty()) - && !phase.isPlayerTurn(ai)) { + if (!phase.is(PhaseType.COMBAT_DECLARE_ATTACKERS) && !phase.isPlayerTurn(ai) || game.getCombat().getAttackers().isEmpty()) { return false; } diff --git a/src/main/java/forge/card/ability/ai/DamagePreventAi.java b/src/main/java/forge/card/ability/ai/DamagePreventAi.java index 2f01f74a1fb..0df72ab9381 100644 --- a/src/main/java/forge/card/ability/ai/DamagePreventAi.java +++ b/src/main/java/forge/card/ability/ai/DamagePreventAi.java @@ -17,6 +17,7 @@ import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; +import forge.game.phase.Combat; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -28,6 +29,7 @@ public class DamagePreventAi extends SpellAbilityAi { protected boolean canPlayAI(Player ai, SpellAbility sa) { final Card hostCard = sa.getSourceCard(); final Game game = ai.getGame(); + final Combat combat = game.getCombat(); boolean chance = false; final Cost cost = sa.getPayCosts(); @@ -69,14 +71,13 @@ public class DamagePreventAi extends SpellAbilityAi { boolean flag = false; for (final Object o : objects) { if (o instanceof Card) { - final Card c = (Card) o; - flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, c); + flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, (Card) o, combat); } else if (o instanceof Player) { // Don't need to worry about Combat Damage during AI's turn final Player p = (Player) o; if (!handler.isPlayerTurn(p)) { - flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, game.getCombat()) && sa - .isAbility()) || ComputerUtilCombat.lifeInDanger(ai, game.getCombat()))); + flag |= (p == ai && ((ComputerUtilCombat.wouldLoseLife(ai, combat) && sa + .isAbility()) || ComputerUtilCombat.lifeInDanger(ai, combat))); } } } @@ -120,8 +121,8 @@ public class DamagePreventAi extends SpellAbilityAi { } // Protect combatants else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { - if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, game.getCombat()) - && (ComputerUtilCombat.lifeInDanger(ai, game.getCombat()) || sa.isAbility()) + if (sa.canTarget(ai) && ComputerUtilCombat.wouldLoseLife(ai, combat) + && (ComputerUtilCombat.lifeInDanger(ai, combat) || sa.isAbility()) && game.getPhaseHandler().isPlayerTurn(ai.getOpponent())) { sa.getTargets().add(ai); chance = true; @@ -138,7 +139,7 @@ public class DamagePreventAi extends SpellAbilityAi { CardLists.sortByEvaluateCreature(combatants); for (final Card c : combatants) { - if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) { + if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) { sa.getTargets().add(c); chance = true; break; @@ -177,8 +178,7 @@ public class DamagePreventAi extends SpellAbilityAi { * a boolean. * @return a boolean. */ - private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, - final boolean mandatory) { + private boolean preventDamageMandatoryTarget(final Player ai, final SpellAbility sa, final boolean mandatory) { final TargetRestrictions tgt = sa.getTargetRestrictions(); sa.resetTargets(); // filter AIs battlefield by what I can target @@ -199,8 +199,9 @@ public class DamagePreventAi extends SpellAbilityAi { final List combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES); CardLists.sortByEvaluateCreature(combatants); if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + Combat combat = game.getCombat(); for (final Card c : combatants) { - if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) { + if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) { sa.getTargets().add(c); return true; } diff --git a/src/main/java/forge/card/ability/ai/DebuffAi.java b/src/main/java/forge/card/ability/ai/DebuffAi.java index 20ca1d693dc..e58ee1c1de6 100644 --- a/src/main/java/forge/card/ability/ai/DebuffAi.java +++ b/src/main/java/forge/card/ability/ai/DebuffAi.java @@ -5,6 +5,8 @@ import java.util.Arrays; import java.util.List; import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import forge.Card; import forge.CardLists; @@ -14,8 +16,10 @@ import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityRestriction; import forge.card.spellability.TargetRestrictions; +import forge.game.Game; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCost; +import forge.game.phase.Combat; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -30,6 +34,7 @@ public class DebuffAi extends SpellAbilityAi { protected boolean canPlayAI(final Player ai, final SpellAbility sa) { // if there is no target and host card isn't in play, don't activate final Card source = sa.getSourceCard(); + final Game game = ai.getGame(); if ((sa.getTargetRestrictions() == null) && !source.isInPlay()) { return false; } @@ -50,12 +55,12 @@ public class DebuffAi extends SpellAbilityAi { } final SpellAbilityRestriction restrict = sa.getRestrictions(); - final PhaseHandler ph = ai.getGame().getPhaseHandler(); + final PhaseHandler ph = game.getPhaseHandler(); // Phase Restrictions if (ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) - || !ai.getGame().getStack().isEmpty()) { + || !game.getStack().isEmpty()) { // Instant-speed pumps should not be cast outside of combat when the // stack is empty if (!SpellAbilityAi.isSorcerySpeed(sa)) { @@ -70,29 +75,28 @@ public class DebuffAi extends SpellAbilityAi { return false; } - if ((sa.getTargetRestrictions() == null) || !sa.getTargetRestrictions().doesTarget()) { + if (!sa.usesTargeting() || !sa.getTargetRestrictions().doesTarget()) { List cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa); - if (!cards.isEmpty()) { - cards = CardLists.filter(cards, new Predicate() { - @Override - public boolean apply(final Card c) { - if ((c.getController().equals(sa.getActivatingPlayer())) || (!c.isBlocking() && !c.isAttacking())) { - return false; - } - // don't add duplicate negative keywords - return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & "))); - } - }); - } - if (cards.isEmpty()) { - return false; - } - } else { - return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList(), false); - } - return true; + final Combat combat = game.getCombat(); + return Iterables.any(cards, new Predicate() { + @Override + public boolean apply(final Card c) { + + if (c.getController().equals(sa.getActivatingPlayer()) || combat == null) + return false; + + if (!combat.isBlocking(c) && !combat.isAttacking(c)) { + return false; + } + // don't add duplicate negative keywords + return sa.hasParam("Keywords") && c.hasAnyKeyword(Arrays.asList(sa.getParam("Keywords").split(" & "))); + } + }); + } else { + return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false); + } } @Override @@ -101,7 +105,7 @@ public class DebuffAi extends SpellAbilityAi { // TODO - copied from AF_Pump.pumpDrawbackAI() - what should be // here? } else { - return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : new ArrayList(), false); + return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false); } return true; @@ -130,7 +134,7 @@ public class DebuffAi extends SpellAbilityAi { final TargetRestrictions tgt = sa.getTargetRestrictions(); sa.resetTargets(); - List list = getCurseCreatures(ai, sa, kws); + List list = getCurseCreatures(ai, sa, kws == null ? Lists.newArrayList() : kws); list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard()); // several uses here: diff --git a/src/main/java/forge/card/ability/ai/EncodeAi.java b/src/main/java/forge/card/ability/ai/EncodeAi.java index 681c186f315..3fafa45916a 100644 --- a/src/main/java/forge/card/ability/ai/EncodeAi.java +++ b/src/main/java/forge/card/ability/ai/EncodeAi.java @@ -71,30 +71,30 @@ public final class EncodeAi extends SpellAbilityAi { * @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean) */ @Override - public Card chooseSingleCard(Player ai, SpellAbility sa, List options, boolean isOptional) { + public Card chooseSingleCard(final Player ai, SpellAbility sa, List options, boolean isOptional) { Card choice = null; - final String logic = sa.getParam("AILogic"); - if (logic == null) { - final List attackers = CardLists.filter(options, new Predicate() { - @Override - public boolean apply(final Card c) { - return CombatUtil.canAttackNextTurn(c); - } - }); - final List unblockables = CardLists.filter(attackers, new Predicate() { - @Override - public boolean apply(final Card c) { - return !CombatUtil.canBeBlocked(c); - } - }); - if (!unblockables.isEmpty()) { - choice = ComputerUtilCard.getBestAI(unblockables); - } else if (!attackers.isEmpty()) { - choice = ComputerUtilCard.getBestAI(attackers); - } else { - choice = ComputerUtilCard.getBestAI(options); +// final String logic = sa.getParam("AILogic"); +// if (logic == null) { + final List attackers = CardLists.filter(options, new Predicate() { + @Override + public boolean apply(final Card c) { + return CombatUtil.canAttackNextTurn(c); } + }); + final List unblockables = CardLists.filter(attackers, new Predicate() { + @Override + public boolean apply(final Card c) { + return !CombatUtil.canBeBlocked(c, ai.getOpponent()); + } + }); + if (!unblockables.isEmpty()) { + choice = ComputerUtilCard.getBestAI(unblockables); + } else if (!attackers.isEmpty()) { + choice = ComputerUtilCard.getBestAI(attackers); + } else { + choice = ComputerUtilCard.getBestAI(options); } +// } return choice; } } diff --git a/src/main/java/forge/card/ability/ai/LifeGainAi.java b/src/main/java/forge/card/ability/ai/LifeGainAi.java index 4f0c2f7e606..3918a5b1bac 100644 --- a/src/main/java/forge/card/ability/ai/LifeGainAi.java +++ b/src/main/java/forge/card/ability/ai/LifeGainAi.java @@ -63,8 +63,7 @@ public class LifeGainAi extends SpellAbilityAi { return false; } boolean lifeCritical = life <= 5; - lifeCritical |= (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DAMAGE) && ComputerUtilCombat - .lifeInDanger(ai, game.getCombat())); + lifeCritical |= game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DAMAGE) && ComputerUtilCombat.lifeInDanger(ai, game.getCombat()); if (abCost != null && !lifeCritical) { if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, false)) { diff --git a/src/main/java/forge/card/ability/ai/ProtectAi.java b/src/main/java/forge/card/ability/ai/ProtectAi.java index 2433e00ecdb..ade0befd1c4 100644 --- a/src/main/java/forge/card/ability/ai/ProtectAi.java +++ b/src/main/java/forge/card/ability/ai/ProtectAi.java @@ -91,19 +91,20 @@ public class ProtectAi extends SpellAbilityAi { return true; } - // is the creature blocking and unable to destroy the attacker - // or would be destroyed itself? - if (c.isBlocking() - && (ComputerUtilCombat.blockerWouldBeDestroyed(ai, c))) { - return true; - } - - // is the creature in blocked and the blocker would survive - // TODO Potential NPE here if no blockers are actually left - if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS) - && combat.isAttacking(c) && game.getCombat().isBlocked(c) - && ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0))) { - return true; + if( combat != null ) { + // is the creature blocking and unable to destroy the attacker + // or would be destroyed itself? + if (combat.isBlocking(c) && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) { + return true; + } + + // is the creature in blocked and the blocker would survive + // TODO Potential NPE here if no blockers are actually left + if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS) + && combat.isAttacking(c) && combat.isBlocked(c) + && ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0), combat)) { + return true; + } } return false; diff --git a/src/main/java/forge/card/ability/ai/PumpAiBase.java b/src/main/java/forge/card/ability/ai/PumpAiBase.java index c97bff1e437..c258693c13c 100644 --- a/src/main/java/forge/card/ability/ai/PumpAiBase.java +++ b/src/main/java/forge/card/ability/ai/PumpAiBase.java @@ -52,6 +52,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { */ public boolean isUsefulCurseKeyword(final Player ai, final String keyword, final Card card, final SpellAbility sa) { final Game game = ai.getGame(); + final Combat combat = game.getCombat(); final PhaseHandler ph = game.getPhaseHandler(); final Player human = ai.getOpponent(); //int attack = getNumAttack(sa); @@ -115,19 +116,18 @@ public abstract class PumpAiBase extends SpellAbilityAi { } } else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.") || keyword.endsWith("Prevent all damage that would be dealt by CARDNAME.")) { - if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || card.isBlocking()) + if (ph.isPlayerTurn(ai) && (!(CombatUtil.canBlock(card) || combat != null && combat.isBlocking(card)) || card.getNetCombatDamage() <= 0 || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) || ph.getPhase().isBefore(PhaseType.MAIN1) || CardLists.getNotKeyword(ai.getCreaturesInPlay(), "Defender").isEmpty())) { return false; } - if (ph.isPlayerTurn(human) && (!card.isAttacking() - || card.getNetCombatDamage() <= 0)) { + if (ph.isPlayerTurn(human) && (combat == null || !combat.isAttacking(card) || card.getNetCombatDamage() <= 0)) { return false; } } else if (keyword.endsWith("CARDNAME attacks each turn if able.")) { - if (ph.isPlayerTurn(ai) || !CombatUtil.canAttack(card, human) || !CombatUtil.canBeBlocked(card) + if (ph.isPlayerTurn(ai) || !CombatUtil.canAttack(card, human) || !CombatUtil.canBeBlocked(card, ai.getOpponent()) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)) { return false; } @@ -135,8 +135,8 @@ public abstract class PumpAiBase extends SpellAbilityAi { if (card.getShield() > 0) { return true; } - if (card.hasKeyword("If CARDNAME would be destroyed, regenerate it.") - && (card.isBlocked() || card.isBlocking())) { + if (card.hasKeyword("If CARDNAME would be destroyed, regenerate it.") && combat != null + && (combat.isBlocked(card) || combat.isBlocking(card))) { return true; } return false; @@ -158,6 +158,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { */ public boolean isUsefulPumpKeyword(final Player ai, final String keyword, final Card card, final SpellAbility sa, final int attack) { final Game game = ai.getGame(); + final Combat combat = game.getCombat(); final PhaseHandler ph = game.getPhaseHandler(); final Player opp = ai.getOpponent(); //int defense = getNumDefense(sa); @@ -171,7 +172,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { final boolean combatRelevant = (keyword.endsWith("First Strike") || keyword.contains("Bushido")); // give evasive keywords to creatures that can or do attack if (evasive) { - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || card.getNetCombatDamage() <= 0 || CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) { @@ -187,7 +188,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { return true; } Predicate flyingOrReach = Predicates.or(CardPredicates.hasKeyword("Flying"), CardPredicates.hasKeyword("Reach")); - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || card.getNetCombatDamage() <= 0 || !Iterables.any(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), @@ -202,7 +203,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { && ComputerUtilCombat.lifeInDanger(ai, game.getCombat())) { return true; } - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || card.getNetCombatDamage() <= 0 || CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), @@ -220,7 +221,6 @@ public abstract class PumpAiBase extends SpellAbilityAi { } else if (keyword.endsWith("Indestructible")) { return true; } else if (keyword.endsWith("Deathtouch")) { - Combat combat = game.getCombat(); if (ph.isPlayerTurn(opp) && ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)) { List attackers = combat.getAttackers(); for (Card attacker : attackers) { @@ -241,34 +241,34 @@ public abstract class PumpAiBase extends SpellAbilityAi { } return false; } else if (combatRelevant) { - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS) || (opp.getCreaturesInPlay().size() < 1) || CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) { return false; } } else if (keyword.equals("Double Strike")) { - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || card.getNetCombatDamage() <= 0 || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; } } else if (keyword.startsWith("Rampage")) { - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).size() < 2) { return false; } } else if (keyword.startsWith("Flanking")) { - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || CardLists.getNotKeyword(CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)), "Flanking").isEmpty()) { return false; } } else if (keyword.startsWith("Trample")) { - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) - || !CombatUtil.canBeBlocked(card) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) + || !CombatUtil.canBeBlocked(card, opp) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || card.getNetCombatDamage() + attack <= 1 || CardLists.filter(opp.getCreaturesInPlay(), CardPredicates.possibleBlockers(card)).isEmpty()) { @@ -278,11 +278,11 @@ public abstract class PumpAiBase extends SpellAbilityAi { if (card.getNetCombatDamage() <= 0) { return false; } - if (card.isBlocking()) { + if (combat != null && combat.isBlocking(card)) { return true; } if ((ph.isPlayerTurn(opp)) - || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) { return false; } @@ -290,20 +290,12 @@ public abstract class PumpAiBase extends SpellAbilityAi { if (card.getNetCombatDamage() <= 0) { return false; } - if (card.isBlocking()) { - return true; - } - if (card.isAttacking() && card.isBlocked()) { - return true; - } - return false; + return combat != null && ( combat.isBlocking(card) || combat.isAttacking(card) && combat.isBlocked(card) ); } else if (keyword.equals("Lifelink")) { if (card.getNetCombatDamage() <= 0) { return false; } - if (!card.isBlocking() && !card.isAttacking()) { - return false; - } + return combat != null && ( combat.isAttacking(card) || combat.isBlocking(card) ); } else if (keyword.equals("Vigilance")) { if (ph.isPlayerTurn(opp) || !CombatUtil.canAttack(card, opp) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) @@ -341,7 +333,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { return false; } } else if (keyword.equals("Islandwalk")) { - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || card.getNetCombatDamage() <= 0 || CardLists.getType(opp.getLandsInPlay(), "Island").isEmpty() @@ -349,7 +341,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { return false; } } else if (keyword.equals("Swampwalk")) { - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || card.getNetCombatDamage() <= 0 || CardLists.getType(opp.getLandsInPlay(), "Swamp").isEmpty() @@ -357,7 +349,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { return false; } } else if (keyword.equals("Mountainwalk")) { - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || card.getNetCombatDamage() <= 0 || CardLists.getType(opp.getLandsInPlay(), "Mountain").isEmpty() @@ -365,7 +357,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { return false; } } else if (keyword.equals("Forestwalk")) { - if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || card.isAttacking()) + if (ph.isPlayerTurn(opp) || !(CombatUtil.canAttack(card, opp) || combat != null && combat.isAttacking(card)) || ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) || card.getNetCombatDamage() <= 0 || CardLists.getType(opp.getLandsInPlay(), "Forest").isEmpty() @@ -385,9 +377,9 @@ public abstract class PumpAiBase extends SpellAbilityAi { protected boolean shouldPumpCard(final Player ai, final SpellAbility sa, final Card c, final int defense, final int attack, final List keywords) { final Game game = ai.getGame(); - final Combat combat = game.getCombat(); - PhaseHandler phase = game.getPhaseHandler(); - + final PhaseHandler phase = game.getPhaseHandler(); + final Combat combat = phase.getCombat(); + if (!c.canBeTargetedBy(sa)) { return false; } @@ -423,14 +415,15 @@ public abstract class PumpAiBase extends SpellAbilityAi { // is the creature blocking and unable to destroy the attacker // or would be destroyed itself? - if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && c.isBlocking()) { - if (defense > 0 && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c)) { + + if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS) && combat.isBlocking(c)) { + if (defense > 0 && ComputerUtilCombat.blockerWouldBeDestroyed(ai, c, combat)) { return true; } - List blockedBy = game.getCombat().getAttackersBlockedBy(c); + List blockedBy = combat.getAttackersBlockedBy(c); // For now, Only care the first creature blocked by a card. // TODO Add in better BlockAdditional support - if (!blockedBy.isEmpty() && attack > 0 && !ComputerUtilCombat.attackerWouldBeDestroyed(ai, blockedBy.get(0))) { + if (!blockedBy.isEmpty() && attack > 0 && !ComputerUtilCombat.attackerWouldBeDestroyed(ai, blockedBy.get(0), combat)) { return true; } } @@ -447,24 +440,20 @@ public abstract class PumpAiBase extends SpellAbilityAi { && combat.isBlocked(c) && combat.getBlockers(c) != null && !combat.getBlockers(c).isEmpty() - && !ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0))) { + && !ComputerUtilCombat.blockerWouldBeDestroyed(ai, combat.getBlockers(c).get(0), combat)) { return true; } - // if the life of the computer is in danger, try to pump blockers blocking Tramplers - List blockedBy = combat.getAttackersBlockedBy(c); - boolean attackerHasTrample = false; - for (Card b : blockedBy) { - attackerHasTrample |= b.hasKeyword("Trample"); - } + if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS, ai.getOpponent()) && combat.isBlocking(c) && defense > 0 ) { + // if the life of the computer is in danger, try to pump blockers blocking Tramplers + List blockedBy = combat.getAttackersBlockedBy(c); + boolean attackerHasTrample = false; + for (Card b : blockedBy) { + attackerHasTrample |= b.hasKeyword("Trample"); + } - if (phase.getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS) - && phase.isPlayerTurn(ai.getOpponent()) - && c.isBlocking() - && defense > 0 - && attackerHasTrample - && (sa.isAbility() || ComputerUtilCombat.lifeInDanger(ai, game.getCombat()))) { - return true; + if (attackerHasTrample && (sa.isAbility() || ComputerUtilCombat.lifeInDanger(ai, combat))) + return true; } return false; @@ -505,6 +494,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { protected List getCurseCreatures(final Player ai, final SpellAbility sa, final int defense, final int attack, final List keywords) { List list = ai.getOpponent().getCreaturesInPlay(); final Game game = ai.getGame(); + final Combat combat = game.getCombat(); list = CardLists.getTargetableCards(list, sa); if (list.isEmpty()) { @@ -538,7 +528,7 @@ public abstract class PumpAiBase extends SpellAbilityAi { list = CardLists.filter(list, new Predicate() { @Override public boolean apply(final Card c) { - if (!c.isAttacking()) { + if (combat == null || !combat.isAttacking(c)) { return false; } if (c.getNetAttack() > 0 && ai.getLife() < 5) { diff --git a/src/main/java/forge/card/ability/ai/PumpAllAi.java b/src/main/java/forge/card/ability/ai/PumpAllAi.java index a2c27ed26e2..fc48b887779 100644 --- a/src/main/java/forge/card/ability/ai/PumpAllAi.java +++ b/src/main/java/forge/card/ability/ai/PumpAllAi.java @@ -15,6 +15,7 @@ import forge.game.Game; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; +import forge.game.phase.Combat; import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -30,6 +31,7 @@ public class PumpAllAi extends PumpAiBase { String valid = ""; final Card source = sa.getSourceCard(); final Game game = ai.getGame(); + final Combat combat = game.getCombat(); final int power = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumAtt"), sa); final int defense = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumDef"), sa); @@ -86,13 +88,12 @@ public class PumpAllAi extends PumpAiBase { } int totalPower = 0; for (Card c : human) { - if (!c.isAttacking()) { + if (combat == null || !combat.isAttacking(c)) { continue; } totalPower += Math.min(c.getNetAttack(), power * -1); - if (phase == PhaseType.COMBAT_DECLARE_BLOCKERS - && game.getCombat().isUnblocked(c)) { - if (ComputerUtilCombat.lifeInDanger(sa.getActivatingPlayer(), game.getCombat())) { + if (phase == PhaseType.COMBAT_DECLARE_BLOCKERS && combat.isUnblocked(c)) { + if (ComputerUtilCombat.lifeInDanger(sa.getActivatingPlayer(), combat)) { return true; } totalPower += Math.min(c.getNetAttack(), power * -1); @@ -124,7 +125,7 @@ public class PumpAllAi extends PumpAiBase { if (power <= 0 && !containsUsefulKeyword(ai, keywords, c, sa, power)) { return false; } - if (phase.equals(PhaseType.COMBAT_DECLARE_ATTACKERS) && c.isAttacking()) { + if (phase == PhaseType.COMBAT_DECLARE_ATTACKERS && combat.isAttacking(c)) { return true; } if (phase.isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && CombatUtil.canAttack(c, opp)) { diff --git a/src/main/java/forge/card/ability/ai/RegenerateAi.java b/src/main/java/forge/card/ability/ai/RegenerateAi.java index 088a88c9d8c..766f70a95b2 100644 --- a/src/main/java/forge/card/ability/ai/RegenerateAi.java +++ b/src/main/java/forge/card/ability/ai/RegenerateAi.java @@ -34,6 +34,7 @@ import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; +import forge.game.phase.Combat; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -61,6 +62,7 @@ public class RegenerateAi extends SpellAbilityAi { final Card hostCard = sa.getSourceCard(); final Cost abCost = sa.getPayCosts(); final Game game = ai.getGame(); + final Combat combat = game.getCombat(); boolean chance = false; if (abCost != null) { @@ -98,7 +100,7 @@ public class RegenerateAi extends SpellAbilityAi { for (final Card c : list) { if (c.getShield() == 0) { - flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, c); + flag |= ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat); } } @@ -142,7 +144,7 @@ public class RegenerateAi extends SpellAbilityAi { CardLists.sortByEvaluateCreature(combatants); for (final Card c : combatants) { - if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) { + if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) { sa.getTargets().add(c); chance = true; break; @@ -196,8 +198,9 @@ public class RegenerateAi extends SpellAbilityAi { final List combatants = CardLists.filter(compTargetables, CardPredicates.Presets.CREATURES); CardLists.sortByEvaluateCreature(combatants); if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + Combat combat = game.getCombat(); for (final Card c : combatants) { - if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) { + if ((c.getShield() == 0) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) { sa.getTargets().add(c); return true; } diff --git a/src/main/java/forge/card/ability/ai/RegenerateAllAi.java b/src/main/java/forge/card/ability/ai/RegenerateAllAi.java index ba884ba4c71..c91d2e8028e 100644 --- a/src/main/java/forge/card/ability/ai/RegenerateAllAi.java +++ b/src/main/java/forge/card/ability/ai/RegenerateAllAi.java @@ -13,6 +13,7 @@ import forge.game.Game; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; +import forge.game.phase.Combat; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -67,9 +68,9 @@ public class RegenerateAllAi extends SpellAbilityAi { } else { if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { final List combatants = CardLists.filter(list, CardPredicates.Presets.CREATURES); - + final Combat combat = game.getCombat(); for (final Card c : combatants) { - if (c.getShield() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c)) { + if (c.getShield() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)) { numSaved++; } } diff --git a/src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java b/src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java index a8fedbd21ef..037dd2ca578 100644 --- a/src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java +++ b/src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java @@ -36,7 +36,7 @@ public class BecomesBlockedEffect extends SpellAbilityEffect { final TargetRestrictions tgt = sa.getTargetRestrictions(); for (final Card c : getTargetCards(sa)) { if ((tgt == null) || c.canBeTargetedBy(sa)) { - game.getCombat().setBlocked(c); + game.getCombat().setBlocked(c, true); if (!c.getDamageHistory().getCreatureGotBlockedThisCombat()) { final HashMap runParams = new HashMap(); runParams.put("Attacker", c); diff --git a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java index aaa22295c56..a5f2a639de2 100644 --- a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java +++ b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java @@ -23,6 +23,7 @@ import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.TargetRestrictions; import forge.card.trigger.TriggerType; import forge.game.Game; +import forge.game.phase.Combat; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; @@ -499,7 +500,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { List defenders = game.getCombat().getDefenders(); if (!defenders.isEmpty()) { // Blockeres are already declared, set this to unblocked - game.getCombat().addAttacker(tgtC, defenders.get(0), false); + game.getCombat().addAttacker(tgtC, defenders.get(0)); } } if (sa.hasParam("Tapped") || sa.hasParam("Ninjutsu")) { @@ -840,9 +841,12 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } if (sa.hasParam("Attacking")) { - final List e = c.getController().getGame().getCombat().getDefenders(); - final GameEntity defender = e.size() == 1 ? e.get(0) : GuiChoose.one("Declare " + c, e); - game.getCombat().addAttacker(c, defender); + final Combat combat = game.getCombat(); + if ( null != combat ) { + final List e = combat.getDefenders(); + final GameEntity defender = e.size() == 1 ? e.get(0) : GuiChoose.one("Declare " + c, e); + combat.addAttacker(c, defender); + } } movedCard = game.getAction().moveTo(c.getController().getZone(destination), c); diff --git a/src/main/java/forge/card/ability/effects/ChooseColorEffect.java b/src/main/java/forge/card/ability/effects/ChooseColorEffect.java index b0ee32eacfe..5b1910c2ccd 100644 --- a/src/main/java/forge/card/ability/effects/ChooseColorEffect.java +++ b/src/main/java/forge/card/ability/effects/ChooseColorEffect.java @@ -100,7 +100,7 @@ public class ChooseColorEffect extends SpellAbilityEffect { final List list = game.getCardsIn(ZoneType.Battlefield); chosen.add(ComputerUtilCard.getMostProminentColor(list)); } - else if (logic.equals("MostProminentAttackers")) { + else if (logic.equals("MostProminentAttackers") && game.getPhaseHandler().inCombat()) { chosen.add(ComputerUtilCard.getMostProminentColor(game.getCombat().getAttackers())); } else if (logic.equals("MostProminentKeywordInComputerDeck")) { diff --git a/src/main/java/forge/card/ability/effects/ChooseSourceEffect.java b/src/main/java/forge/card/ability/effects/ChooseSourceEffect.java index 6edfe7d024a..adcf8122dc7 100644 --- a/src/main/java/forge/card/ability/effects/ChooseSourceEffect.java +++ b/src/main/java/forge/card/ability/effects/ChooseSourceEffect.java @@ -18,6 +18,7 @@ import forge.card.spellability.TargetRestrictions; import forge.game.Game; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; +import forge.game.phase.Combat; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -138,7 +139,7 @@ public class ChooseSourceEffect extends SpellAbilityEffect { sourcesToChooseFrom.remove(o); } else { - if (sa.hasParam("AILogic") && sa.getParam("AILogic").equals("NeedsPrevention")) { + if ("NeedsPrevention".equals(sa.getParam("AILogic"))) { final Player ai = sa.getActivatingPlayer(); if (!game.getStack().isEmpty()) { Card choseCard = ChooseCardOnStack(sa, ai, game); @@ -147,14 +148,15 @@ public class ChooseSourceEffect extends SpellAbilityEffect { } } + final Combat combat = game.getCombat(); if (chosen.isEmpty()) { permanentSources = CardLists.filter(permanentSources, new Predicate() { @Override public boolean apply(final Card c) { - if (!c.isAttacking(ai) || !game.getCombat().isUnblocked(c)) { + if (combat == null || !combat.isAttacking(c, ai) || !combat.isUnblocked(c)) { return false; } - return ComputerUtilCombat.damageIfUnblocked(c, ai, game.getCombat()) > 0; + return ComputerUtilCombat.damageIfUnblocked(c, ai, combat) > 0; } }); chosen.add(ComputerUtilCard.getBestCreatureAI(permanentSources)); diff --git a/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java b/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java index 8fdcf895b62..b07716ee3da 100644 --- a/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java +++ b/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java @@ -248,9 +248,8 @@ public class CopyPermanentEffect extends SpellAbilityEffect { if (sa.hasParam("Tapped")) { copy.setTapped(true); } - if (sa.hasParam("CopyAttacking")) { - final GameEntity defender = (GameEntity) AbilityUtils.getDefinedPlayers(hostCard, - sa.getParam("CopyAttacking"), sa).get(0); + if (sa.hasParam("CopyAttacking") && game.getPhaseHandler().inCombat()) { + final GameEntity defender = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("CopyAttacking"), sa).get(0); game.getCombat().addAttacker(copy, defender); } } diff --git a/src/main/java/forge/card/ability/effects/EndTurnEffect.java b/src/main/java/forge/card/ability/effects/EndTurnEffect.java index bd14ba684cc..91bc3b55c0a 100644 --- a/src/main/java/forge/card/ability/effects/EndTurnEffect.java +++ b/src/main/java/forge/card/ability/effects/EndTurnEffect.java @@ -30,7 +30,7 @@ public class EndTurnEffect extends SpellAbilityEffect { game.getStack().clear(); // 2) All attacking and blocking creatures are removed from combat. - game.getCombat().reset(game.getPhaseHandler().getPlayerTurn()); + game.getPhaseHandler().endCombat(); // 3) State-based actions are checked. No player gets priority, and no // triggered abilities are put onto the stack. diff --git a/src/main/java/forge/card/ability/effects/RemoveFromCombatEffect.java b/src/main/java/forge/card/ability/effects/RemoveFromCombatEffect.java index 4e7220024e3..74139177308 100644 --- a/src/main/java/forge/card/ability/effects/RemoveFromCombatEffect.java +++ b/src/main/java/forge/card/ability/effects/RemoveFromCombatEffect.java @@ -31,11 +31,10 @@ public class RemoveFromCombatEffect extends SpellAbilityEffect { final Player activator = sa.getActivatingPlayer(); final Game game = activator.getGame(); - final TargetRestrictions tgt = sa.getTargetRestrictions(); for (final Card c : getTargetCards(sa)) { - if ((tgt == null) || c.canBeTargetedBy(sa)) { - game.getCombat().removeFromCombat(c); + if ((tgt == null) || c.canBeTargetedBy(sa) && game.getPhaseHandler().inCombat()) { + game.getPhaseHandler().getCombat().removeFromCombat(c); } } diff --git a/src/main/java/forge/card/ability/effects/TokenEffect.java b/src/main/java/forge/card/ability/effects/TokenEffect.java index d0857600348..cf57089708a 100644 --- a/src/main/java/forge/card/ability/effects/TokenEffect.java +++ b/src/main/java/forge/card/ability/effects/TokenEffect.java @@ -32,6 +32,7 @@ import forge.card.trigger.Trigger; import forge.card.trigger.TriggerHandler; import forge.game.Game; import forge.game.event.GameEventTokenCreated; +import forge.game.phase.Combat; import forge.game.player.Player; import forge.gui.GuiChoose; import forge.item.PaperToken; @@ -281,14 +282,16 @@ public class TokenEffect extends SpellAbilityEffect { if (this.tokenTapped) { c.setTapped(true); } - if (this.tokenAttacking) { - final List defs = c.getController().getGame().getCombat().getDefenders(); + if (this.tokenAttacking && game.getPhaseHandler().inCombat()) { + Combat combat = game.getPhaseHandler().getCombat(); + final List defs = combat.getDefenders(); + final GameEntity defender; if (c.getController().isHuman()) { - final GameEntity defender = defs.size() == 1 ? defs.get(0) : GuiChoose.one("Declare " + c, defs); - game.getCombat().addAttacker(c, defender); + defender = defs.size() == 1 ? defs.get(0) : GuiChoose.one("Declare " + c, defs); } else { - game.getCombat().addAttacker(c, defs.get(0)); + defender = defs.get(0); } + combat.addAttacker(c, defender); } if (remember != null) { game.getCardState(sa.getSourceCard()).addRemembered(c); diff --git a/src/main/java/forge/game/Game.java b/src/main/java/forge/game/Game.java index 0dac2ef6be1..82e008cbf6d 100644 --- a/src/main/java/forge/game/Game.java +++ b/src/main/java/forge/game/Game.java @@ -71,7 +71,6 @@ public class Game { private final StaticEffects staticEffects = new StaticEffects(); private final TriggerHandler triggerHandler = new TriggerHandler(this); private final ReplacementHandler replacementHandler = new ReplacementHandler(this); - private Combat combat = new Combat(); private final EventBus events = new EventBus(); private final GameLog gameLog = new GameLog(); private final ColorChanger colorChanger = new ColorChanger(); @@ -228,18 +227,10 @@ public class Game { * @return the combat */ public final Combat getCombat() { - return this.combat; + return this.getPhaseHandler().getCombat(); } - /** - * Sets the combat. - * - * @param combat0 - * the combat to set - */ - public final void setCombat(final Combat combat0) { - this.combat = combat0; - } + /** * Gets the game log. diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index 236c9ea7683..05280ea25b3 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -222,7 +222,7 @@ public class GameAction { } if (zoneFrom != null) { - if (zoneFrom.is(ZoneType.Battlefield) && c.isCreature()) { + if (zoneFrom.is(ZoneType.Battlefield) && c.isCreature() && game.getCombat() != null) { game.getCombat().removeFromCombat(c); } zoneFrom.remove(c); @@ -1348,7 +1348,8 @@ public class GameAction { final boolean persist = (c.hasKeyword("Persist") && (c.getCounters(CounterType.M1M1) == 0)) && !c.isToken(); final boolean undying = (c.hasKeyword("Undying") && (c.getCounters(CounterType.P1P1) == 0)) && !c.isToken(); - game.getCombat().removeFromCombat(c); + if (game.getPhaseHandler().inCombat()) + game.getPhaseHandler().getCombat().removeFromCombat(c); final Card newCard = this.moveToGraveyard(c); diff --git a/src/main/java/forge/game/ai/AiAttackController.java b/src/main/java/forge/game/ai/AiAttackController.java index c4a85b10d0e..b17c0f0dd74 100644 --- a/src/main/java/forge/game/ai/AiAttackController.java +++ b/src/main/java/forge/game/ai/AiAttackController.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Random; import com.google.common.base.Predicate; +import com.google.common.collect.Lists; import forge.Card; import forge.CardLists; @@ -29,7 +30,6 @@ import forge.CounterType; import forge.GameEntity; import forge.card.trigger.Trigger; import forge.card.trigger.TriggerType; -import forge.game.Game; import forge.game.phase.Combat; import forge.game.phase.CombatUtil; import forge.game.player.Player; @@ -58,11 +58,10 @@ public class AiAttackController { private List myList; // holds computer creatures private final Player ai; - private final Player opponent; private int aiAggression = 0; // added by Masher, how aggressive the ai is // attack will be depending on circumstances - + /** *

@@ -74,11 +73,12 @@ public class AiAttackController { * @param possibleBlockers * a {@link forge.CardList} object. */ - public AiAttackController(final Player ai, final Player opponent) { + public AiAttackController(final Player ai) { this.ai = ai; - this.opponent = opponent; + Player opponent = ai.getOpponent(); - this.oppList = opponent.getCreaturesInPlay(); + this.oppList = Lists.newArrayList(); + this.oppList.addAll(opponent.getCreaturesInPlay()); this.myList = ai.getCreaturesInPlay(); @@ -342,7 +342,7 @@ public class AiAttackController { if (!CombatUtil.canAttackNextTurn(attacker)) { continue; } - if (blockersLeft > 0 && CombatUtil.canBeBlocked(attacker)) { + if (blockersLeft > 0 && CombatUtil.canBeBlocked(attacker, ai)) { blockersLeft--; continue; } @@ -442,7 +442,7 @@ public class AiAttackController { * @param bAssault * a boolean. */ - public final GameEntity chooseDefender(final Combat c, final Combat gameCombat, final boolean bAssault) { + private final GameEntity chooseDefender(final Combat c, final boolean bAssault) { final List defs = c.getDefenders(); if (defs.size() == 1) { return defs.get(0); @@ -450,8 +450,7 @@ public class AiAttackController { final GameEntity entity = ai.getMustAttackEntity(); if (null != entity) { - final List defenders = gameCombat.getDefenders(); - int n = defenders.indexOf(entity); + int n = defs.indexOf(entity); if (-1 == n) { System.out.println("getMustAttackEntity() returned something not in defenders."); return defs.get(0); @@ -483,29 +482,22 @@ public class AiAttackController { * * @return a {@link forge.game.phase.Combat} object. */ - public final Combat getAttackers() { + public final void declareAttackers(final Combat combat) { // if this method is called multiple times during a turn, // it will always return the same value // randomInt is used so that the computer doesn't always // do the same thing on turn 3 if he had the same creatures in play // I know this is a little confusing - Game game = ai.getGame(); - - random.setSeed(game.getPhaseHandler().getTurn() + AiAttackController.randomInt); - - final Combat combat = new Combat(); - combat.setAttackingPlayer(game.getCombat().getAttackingPlayer()); - - game.getCombat().initiatePossibleDefenders(opponent); - combat.setDefenders(game.getCombat().getDefenders()); + + random.setSeed(ai.getGame().getPhaseHandler().getTurn() + AiAttackController.randomInt); if (this.attackers.isEmpty()) { - return combat; + return; } final boolean bAssault = this.doAssault(ai); // Determine who will be attacked - GameEntity defender = this.chooseDefender(combat, game.getCombat(), bAssault); + GameEntity defender = this.chooseDefender(combat, bAssault); List attackersLeft = new ArrayList(this.attackers); // Attackers that don't really have a choice for (final Card attacker : this.attackers) { @@ -529,7 +521,7 @@ public class AiAttackController { } } if (attackersLeft.isEmpty()) { - return combat; + return; } if (bAssault) { if ( LOG_AI_ATTACKS ) @@ -540,7 +532,7 @@ public class AiAttackController { combat.addAttacker(attacker, defender); } } - return combat; + return; } // Exalted @@ -552,8 +544,7 @@ public class AiAttackController { exalted = true; break; } - if (c.getName().equals("Finest Hour") - && game.getPhaseHandler().isFirstCombat()) { + if (c.getName().equals("Finest Hour") && ai.getGame().getPhaseHandler().isFirstCombat()) { exalted = true; break; } @@ -573,7 +564,7 @@ public class AiAttackController { for (Card attacker : this.attackers) { if (CombatUtil.canAttack(attacker, defender, combat) && this.shouldAttack(ai, attacker, this.blockers, combat)) { combat.addAttacker(attacker, defender); - return combat; + return; } } } @@ -821,8 +812,6 @@ public class AiAttackController { } } } - - return combat; } // getAttackers() /** diff --git a/src/main/java/forge/game/ai/ComputerUtilBlock.java b/src/main/java/forge/game/ai/AiBlockController.java similarity index 61% rename from src/main/java/forge/game/ai/ComputerUtilBlock.java rename to src/main/java/forge/game/ai/AiBlockController.java index a2ec8a92d7d..30433701cc8 100644 --- a/src/main/java/forge/game/ai/ComputerUtilBlock.java +++ b/src/main/java/forge/game/ai/AiBlockController.java @@ -41,162 +41,36 @@ import forge.game.player.Player; * @author Forge * @version $Id$ */ -public class ComputerUtilBlock { +public class AiBlockController { + + private final Player ai; /** Constant attackers. */ - private static List attackers = new ArrayList(); // all attackers + private List attackers = new ArrayList(); // all attackers /** Constant attackersLeft. */ - private static List attackersLeft = new ArrayList(); // keeps track of + private List attackersLeft = new ArrayList(); // keeps track of // all currently // unblocked // attackers /** Constant blockedButUnkilled. */ - private static List blockedButUnkilled = new ArrayList(); // blocked + private List blockedButUnkilled = new ArrayList(); // blocked // attackers // that // currently // wouldn't be // destroyed /** Constant blockersLeft. */ - private static List blockersLeft = new ArrayList(); // keeps track of all + private List blockersLeft = new ArrayList(); // keeps track of all // unassigned // blockers - /** Constant diff=0. */ - private static int diff = 0; + private int diff = 0; - private static boolean lifeInDanger = false; - - /** - *

- * Getter for the field attackers. - *

- * - * @return a {@link forge.CardList} object. - */ - private static List getAttackers() { - return ComputerUtilBlock.attackers; + private boolean lifeInDanger = false; + public AiBlockController(Player aiPlayer) { + this.ai = aiPlayer; } - - /** - *

- * Setter for the field attackers. - *

- * - * @param cardList - * a {@link forge.CardList} object. - */ - private static void setAttackers(final List cardList) { - ComputerUtilBlock.attackers = (cardList); - } - - /** - *

- * Getter for the field attackersLeft. - *

- * - * @return a {@link forge.CardList} object. - */ - private static List getAttackersLeft() { - return ComputerUtilBlock.attackersLeft; - } - - /** - *

- * Setter for the field attackersLeft. - *

- * - * @param cardList - * a {@link forge.CardList} object. - */ - private static void setAttackersLeft(final List cardList) { - ComputerUtilBlock.attackersLeft = (cardList); - } - - /** - *

- * Getter for the field blockedButUnkilled. - *

- * - * @return a {@link forge.CardList} object. - */ - private static List getBlockedButUnkilled() { - return ComputerUtilBlock.blockedButUnkilled; - - } - - /** - *

- * Setter for the field blockedButUnkilled. - *

- * - * @param cardList - * a {@link forge.CardList} object. - */ - private static void setBlockedButUnkilled(final List cardList) { - ComputerUtilBlock.blockedButUnkilled = (cardList); - } - - /** - *

- * Getter for the field blockersLeft. - *

- * - * @return a {@link forge.CardList} object. - */ - private static List getBlockersLeft() { - return ComputerUtilBlock.blockersLeft; - } - - /** - *

- * Setter for the field blockersLeft. - *

- * - * @param cardList - * a {@link forge.CardList} object. - */ - private static void setBlockersLeft(final List cardList) { - ComputerUtilBlock.blockersLeft = (cardList); - } - - /** - *

- * Getter for the field diff. - *

- * - * @return a int. - */ - private static int getDiff() { - return ComputerUtilBlock.diff; - } - - /** - *

- * Setter for the field diff. - *

- * - * @param diff - * a int. - */ - private static void setDiff(final int diff) { - ComputerUtilBlock.diff = (diff); - } - + // finds the creatures able to block the attacker - /** - *

- * getPossibleBlockers. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param blockersLeft - * a {@link forge.CardList} object. - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a {@link forge.CardList} object. - */ - private static List getPossibleBlockers(final Card attacker, final List blockersLeft, final Combat combat - , final boolean solo) { + private List getPossibleBlockers(final Combat combat, final Card attacker, final List blockersLeft, final boolean solo) { final List blockers = new ArrayList(); for (final Card blocker : blockersLeft) { @@ -214,20 +88,7 @@ public class ComputerUtilBlock { } // finds blockers that won't be destroyed - /** - *

- * getSafeBlockers. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param blockersLeft - * a {@link forge.CardList} object. - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a {@link forge.CardList} object. - */ - private static List getSafeBlockers(final Player ai, final Card attacker, final List blockersLeft, final Combat combat) { + private List getSafeBlockers(final Combat combat, final Card attacker, final List blockersLeft) { final List blockers = new ArrayList(); for (final Card b : blockersLeft) { @@ -240,20 +101,7 @@ public class ComputerUtilBlock { } // finds blockers that destroy the attacker - /** - *

- * getKillingBlockers. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param blockersLeft - * a {@link forge.CardList} object. - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a {@link forge.CardList} object. - */ - private static List getKillingBlockers(final Player ai, final Card attacker, final List blockersLeft, final Combat combat) { + private List getKillingBlockers(final Combat combat, final Card attacker, final List blockersLeft) { final List blockers = new ArrayList(); for (final Card b : blockersLeft) { @@ -267,7 +115,7 @@ public class ComputerUtilBlock { - public final static List> sortAttackerByDefender(Combat combat) { + private List> sortAttackerByDefender(final Combat combat) { List defenders = combat.getDefenders(); final ArrayList> attackers = new ArrayList>(defenders.size()); for (GameEntity defender : defenders) { @@ -276,7 +124,7 @@ public class ComputerUtilBlock { return attackers; } - public static List sortPotentialAttackers(final Player ai, final Combat combat) { + private List sortPotentialAttackers(final Combat combat) { final List> attackerLists = sortAttackerByDefender(combat); final List sortedAttackers = new ArrayList(); final List firstAttacker = attackerLists.get(0); @@ -328,20 +176,11 @@ public class ComputerUtilBlock { // ================================ // Good Blocks means a good trade or no trade - /** - *

- * makeGoodBlocks. - *

- * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a {@link forge.game.phase.Combat} object. - */ - private static Combat makeGoodBlocks(final Player ai, final Combat combat) { + private void makeGoodBlocks(final Combat combat) { - List currentAttackers = new ArrayList(ComputerUtilBlock.getAttackersLeft()); + List currentAttackers = new ArrayList(attackersLeft); - for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) { + for (final Card attacker : attackersLeft) { if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { continue; @@ -349,26 +188,25 @@ public class ComputerUtilBlock { Card blocker = null; - final List blockers = ComputerUtilBlock.getPossibleBlockers(attacker, - ComputerUtilBlock.getBlockersLeft(), combat, true); + final List blockers = getPossibleBlockers(combat, attacker, blockersLeft, true); - final List safeBlockers = ComputerUtilBlock.getSafeBlockers(ai, attacker, blockers, combat); + final List safeBlockers = getSafeBlockers(combat, attacker, blockers); List killingBlockers; if (safeBlockers.size() > 0) { // 1.Blockers that can destroy the attacker but won't get // destroyed - killingBlockers = ComputerUtilBlock.getKillingBlockers(ai, attacker, safeBlockers, combat); + killingBlockers = getKillingBlockers(combat, attacker, safeBlockers); if (killingBlockers.size() > 0) { blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); } else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers); - ComputerUtilBlock.getBlockedButUnkilled().add(attacker); + blockedButUnkilled.add(attacker); } } // no safe blockers else { // 3.Blockers that can destroy the attacker and have an upside when dying - killingBlockers = ComputerUtilBlock.getKillingBlockers(ai, attacker, blockers, combat); + killingBlockers = getKillingBlockers(combat, attacker, blockers); for (Card b : killingBlockers) { if ((b.hasKeyword("Undying") && b.getCounters(CounterType.P1P1) == 0) || !b.getSVar("SacMe").equals("")) { @@ -380,7 +218,7 @@ public class ComputerUtilBlock { if (blocker == null && killingBlockers.size() > 0) { final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers); - if ((ComputerUtilCard.evaluateCreature(worst) + ComputerUtilBlock.getDiff()) < ComputerUtilCard + if ((ComputerUtilCard.evaluateCreature(worst) + diff) < ComputerUtilCard .evaluateCreature(attacker)) { blocker = worst; } @@ -391,8 +229,7 @@ public class ComputerUtilBlock { combat.addBlocker(attacker, blocker); } } - ComputerUtilBlock.setAttackersLeft(new ArrayList(currentAttackers)); - return combat; + attackersLeft = (new ArrayList(currentAttackers)); } // Good Gang Blocks means a good trade or no trade @@ -407,14 +244,14 @@ public class ComputerUtilBlock { */ static final Predicate rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT")); - private static Combat makeGangBlocks(final Player ai, final Combat combat) { - List currentAttackers = CardLists.filter(ComputerUtilBlock.getAttackersLeft(), Predicates.not(rampagesOrNeedsManyToBlock)); + private void makeGangBlocks(final Combat combat) { + List currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock)); List blockers; // Try to block an attacker without first strike with a gang of first strikers - for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) { + for (final Card attacker : attackersLeft) { if (!attacker.hasKeyword("First Strike") && !attacker.hasKeyword("Double Strike")) { - blockers = ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, false); + blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); final List firstStrikeBlockers = new ArrayList(); final List blockGang = new ArrayList(); for (int i = 0; i < blockers.size(); i++) { @@ -448,12 +285,12 @@ public class ComputerUtilBlock { } } - ComputerUtilBlock.setAttackersLeft(new ArrayList(currentAttackers)); - currentAttackers = new ArrayList(ComputerUtilBlock.getAttackersLeft()); + attackersLeft = (new ArrayList(currentAttackers)); + currentAttackers = new ArrayList(attackersLeft); // Try to block an attacker with two blockers of which only one will die - for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) { - blockers = ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, false); + for (final Card attacker : attackersLeft) { + blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); List usableBlockers; final List blockGang = new ArrayList(); int absorbedDamage = 0; // The amount of damage needed to kill the @@ -471,12 +308,11 @@ public class ComputerUtilBlock { && !(c.hasKeyword("First Strike") || c.hasKeyword("Double Strike"))) { return false; } - return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + ComputerUtilBlock.getDiff()) < ComputerUtilCard - .evaluateCreature(attacker); + return lifeInDanger || (ComputerUtilCard.evaluateCreature(c) + diff) < ComputerUtilCard.evaluateCreature(attacker); } }); if (usableBlockers.size() < 2) { - return combat; + return; } final Card leader = ComputerUtilCard.getBestCreatureAI(usableBlockers); @@ -516,8 +352,7 @@ public class ComputerUtilBlock { } } - ComputerUtilBlock.setAttackersLeft(new ArrayList(currentAttackers)); - return combat; + attackersLeft = (new ArrayList(currentAttackers)); } // Bad Trade Blocks (should only be made if life is in danger) @@ -530,68 +365,56 @@ public class ComputerUtilBlock { * a {@link forge.game.phase.Combat} object. * @return a {@link forge.game.phase.Combat} object. */ - private static Combat makeTradeBlocks(final Player ai, final Combat combat) { + private void makeTradeBlocks(final Combat combat) { - List currentAttackers = new ArrayList(ComputerUtilBlock.getAttackersLeft()); + List currentAttackers = new ArrayList(attackersLeft); List killingBlockers; - for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) { + for (final Card attacker : attackersLeft) { if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { continue; } - killingBlockers = ComputerUtilBlock.getKillingBlockers(ai, attacker, - ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, true), - combat); + List possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); + killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers); if ((killingBlockers.size() > 0) && ComputerUtilCombat.lifeInDanger(ai, combat)) { final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); combat.addBlocker(attacker, blocker); currentAttackers.remove(attacker); } } - ComputerUtilBlock.setAttackersLeft(new ArrayList(currentAttackers)); - return combat; + attackersLeft = (new ArrayList(currentAttackers)); + } // Chump Blocks (should only be made if life is in danger) - /** - *

- * makeChumpBlocks. - *

- * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a {@link forge.game.phase.Combat} object. - */ - private static Combat makeChumpBlocks(final Player ai, final Combat combat) { + private void makeChumpBlocks(final Combat combat) { - List currentAttackers = new ArrayList(ComputerUtilBlock.getAttackersLeft()); + List currentAttackers = new ArrayList(attackersLeft); - Combat newCombat = makeChumpBlocks(ai, combat, currentAttackers); + makeChumpBlocks(combat, currentAttackers); - if (ComputerUtilCombat.lifeInDanger(ai, newCombat)) { - return makeMultiChumpBlocks(ai, newCombat); + if (ComputerUtilCombat.lifeInDanger(ai, combat)) { + makeMultiChumpBlocks(combat); } - - return newCombat; } - private static Combat makeChumpBlocks(final Player ai, final Combat combat, List attackers) { + private void makeChumpBlocks(final Combat combat, List attackers) { if (attackers.isEmpty() || !ComputerUtilCombat.lifeInDanger(ai, combat)) { - return combat; + return; } Card attacker = attackers.get(0); if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { attackers.remove(0); - return makeChumpBlocks(ai, combat, attackers); + makeChumpBlocks(combat, attackers); + return; } - List chumpBlockers = ComputerUtilBlock - .getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, true); + List chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); if (!chumpBlockers.isEmpty()) { final Card blocker = ComputerUtilCard.getWorstCreatureAI(chumpBlockers); @@ -607,44 +430,35 @@ public class ComputerUtilBlock { && !other.hasKeyword("Trample") && CombatUtil.canBlock(other, blocker, combat)) { combat.addBlocker(other, blocker); - ComputerUtilBlock.getAttackersLeft().remove(other); - ComputerUtilBlock.getBlockedButUnkilled().add(other); + attackersLeft.remove(other); + blockedButUnkilled.add(other); attackers.remove(other); - return makeChumpBlocks(ai, combat, attackers); + makeChumpBlocks(combat, attackers); + return; } } } } combat.addBlocker(attacker, blocker); - ComputerUtilBlock.getAttackersLeft().remove(attacker); - ComputerUtilBlock.getBlockedButUnkilled().add(attacker); + attackersLeft.remove(attacker); + blockedButUnkilled.add(attacker); } attackers.remove(0); - return makeChumpBlocks(ai, combat, attackers); + makeChumpBlocks(combat, attackers); } - /** - *

- * makeMultiChumpBlocks. - *

- * - * Block creatures with "can't be blocked except by two or more creatures" - * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a {@link forge.game.phase.Combat} object. - */ - private static Combat makeMultiChumpBlocks(final Player ai, final Combat combat) { + // Block creatures with "can't be blocked except by two or more creatures" + private void makeMultiChumpBlocks(final Combat combat) { - List currentAttackers = new ArrayList(ComputerUtilBlock.getAttackersLeft()); + List currentAttackers = new ArrayList(attackersLeft); for (final Card attacker : currentAttackers) { if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { continue; } - List possibleBlockers = ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, true); + List possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); if (!CombatUtil.canAttackerBeBlockedWithAmount(attacker, possibleBlockers.size())) { continue; } @@ -659,33 +473,21 @@ public class ComputerUtilBlock { } } if (CombatUtil.canAttackerBeBlockedWithAmount(attacker, usedBlockers.size())) { - ComputerUtilBlock.getAttackersLeft().remove(attacker); + attackersLeft.remove(attacker); } else { for (Card blocker : usedBlockers) { combat.removeBlockAssignment(attacker, blocker); } } } - - return combat; } - // Reinforce blockers blocking attackers with trample (should only be made - // if life is in danger) - /** - *

- * reinforceBlockersAgainstTrample. - *

- * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a {@link forge.game.phase.Combat} object. - */ - private static Combat reinforceBlockersAgainstTrample(final Player ai, final Combat combat) { + /** Reinforce blockers blocking attackers with trample (should only be made if life is in danger) */ + private void reinforceBlockersAgainstTrample(final Combat combat) { List chumpBlockers; - List tramplingAttackers = CardLists.getKeyword(ComputerUtilBlock.getAttackers(), "Trample"); + List tramplingAttackers = CardLists.getKeyword(attackers, "Trample"); tramplingAttackers = CardLists.filter(tramplingAttackers, Predicates.not(rampagesOrNeedsManyToBlock)); // TODO - should check here for a "rampage-like" trigger that replaced @@ -699,8 +501,7 @@ public class ComputerUtilBlock { continue; } - chumpBlockers = ComputerUtilBlock - .getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, false); + chumpBlockers = getPossibleBlockers(combat, attacker, blockersLeft, false); chumpBlockers.removeAll(combat.getBlockers(attacker)); for (final Card blocker : chumpBlockers) { // Add an additional blocker if the current blockers are not @@ -712,38 +513,26 @@ public class ComputerUtilBlock { } } } - - return combat; } - // Support blockers not destroying the attacker with more blockers to try to - // kill the attacker - /** - *

- * reinforceBlockersToKill. - *

- * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @return a {@link forge.game.phase.Combat} object. - */ - private static Combat reinforceBlockersToKill(final Player ai, final Combat combat) { + /** Support blockers not destroying the attacker with more blockers to try to kill the attacker */ + private void reinforceBlockersToKill(final Combat combat) { List safeBlockers; List blockers; - List targetAttackers = CardLists.filter(ComputerUtilBlock.getBlockedButUnkilled(), Predicates.not(rampagesOrNeedsManyToBlock)); + List targetAttackers = CardLists.filter(blockedButUnkilled, Predicates.not(rampagesOrNeedsManyToBlock)); // TODO - should check here for a "rampage-like" trigger that replaced // the keyword: // "Whenever CARDNAME becomes blocked, it gets +1/+1 until end of turn for each creature blocking it." for (final Card attacker : targetAttackers) { - blockers = ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, false); + blockers = getPossibleBlockers(combat, attacker, blockersLeft, false); blockers.removeAll(combat.getBlockers(attacker)); // Try to use safe blockers first - safeBlockers = ComputerUtilBlock.getSafeBlockers(ai, attacker, blockers, combat); + safeBlockers = getSafeBlockers(combat, attacker, blockers); for (final Card blocker : safeBlockers) { final int damageNeeded = ComputerUtilCombat.getDamageToKill(attacker) + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, blocker, combat, false); @@ -777,198 +566,153 @@ public class ComputerUtilBlock { final int additionalDamage = ComputerUtilCombat.dealsDamageAsBlocker(attacker, blocker); if ((damageNeeded > currentDamage) && !(damageNeeded > (currentDamage + additionalDamage)) - && ((ComputerUtilCard.evaluateCreature(blocker) + ComputerUtilBlock.getDiff()) < ComputerUtilCard + && ((ComputerUtilCard.evaluateCreature(blocker) + diff) < ComputerUtilCard .evaluateCreature(attacker)) && CombatUtil.canBlock(attacker, blocker, combat)) { combat.addBlocker(attacker, blocker); - ComputerUtilBlock.getBlockersLeft().remove(blocker); + blockersLeft.remove(blocker); } } } - - return combat; } - /** - *

- * resetBlockers. - *

- * - * @param combat - * a {@link forge.game.phase.Combat} object. - * @param possibleBlockers - * a {@link forge.CardList} object. - * @return a {@link forge.game.phase.Combat} object. - */ - private static Combat resetBlockers(final Combat combat, final List possibleBlockers) { + private void clearBlockers(final Combat combat, final List possibleBlockers) { final List oldBlockers = combat.getAllBlockers(); for (final Card blocker : oldBlockers) { - combat.removeFromCombat(blocker); + if ( blocker.getController() == ai ) // don't touch other player's blockers + combat.removeFromCombat(blocker); } - ComputerUtilBlock.setAttackersLeft(new ArrayList(ComputerUtilBlock.getAttackers())); // keeps - // track - // of all - // currently - // unblocked - // attackers - ComputerUtilBlock.setBlockersLeft(new ArrayList(possibleBlockers)); // keeps - // track of - // all - // unassigned - // blockers - ComputerUtilBlock.setBlockedButUnkilled(new ArrayList()); // keeps track - // of all - // blocked - // attackers that currently - // wouldn't be destroyed - - return combat; + attackersLeft = new ArrayList(attackers); // keeps track of all currently unblocked attackers + blockersLeft = new ArrayList(possibleBlockers); // keeps track of all unassigned blockers + blockedButUnkilled = new ArrayList(); // keeps track of all blocked attackers that currently wouldn't be destroyed } - // Main function - /** - *

- * getBlockers. - *

- * - * @param originalCombat - * a {@link forge.game.phase.Combat} object. - * @param possibleBlockers - * a {@link forge.CardList} object. - * @return a {@link forge.game.phase.Combat} object. - */ - public static Combat getBlockers(final Player ai, final Combat originalCombat, final List possibleBlockers) { + /** Assigns blockers for the provided combat instance (in favor of player passes to ctor) */ + public void assignBlockers(final Combat combat) { + + final List possibleBlockers = ai.getCreaturesInPlay(); - Combat combat = originalCombat; + attackers = sortPotentialAttackers(combat); - ComputerUtilBlock.setAttackers(ComputerUtilBlock.sortPotentialAttackers(ai, combat)); - - if (ComputerUtilBlock.getAttackers().size() == 0) { - return combat; + if (attackers.isEmpty()) { + return; } - // keeps track of all currently unblocked attackers - ComputerUtilBlock.setAttackersLeft(new ArrayList(ComputerUtilBlock.getAttackers())); - // keeps track of all unassigned blockers - ComputerUtilBlock.setBlockersLeft(new ArrayList(possibleBlockers)); - // keeps track of all blocked attackers that currently wouldn't be destroyed - ComputerUtilBlock.setBlockedButUnkilled(new ArrayList()); + clearBlockers(combat, possibleBlockers); + List blockers; List chumpBlockers; - ComputerUtilBlock.setDiff((ai.getLife() * 2) - 5); // This is the minimal gain for an unnecessary trade + diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade // remove all attackers that can't be blocked anyway - for (final Card a : ComputerUtilBlock.getAttackers()) { - if (!CombatUtil.canBeBlocked(a)) { - ComputerUtilBlock.getAttackersLeft().remove(a); + for (final Card a : attackers) { + if (!CombatUtil.canBeBlocked(a, ai)) { + attackersLeft.remove(a); } } // remove all blockers that can't block anyway for (final Card b : possibleBlockers) { if (!CombatUtil.canBlock(b, combat)) { - ComputerUtilBlock.getBlockersLeft().remove(b); + blockersLeft.remove(b); } } - if (ComputerUtilBlock.getAttackersLeft().size() == 0) { - return combat; + if (attackersLeft.isEmpty()) { + return; } // Begin with the weakest blockers - CardLists.sortByPowerAsc(ComputerUtilBlock.getBlockersLeft()); + CardLists.sortByPowerAsc(blockersLeft); // == 1. choose best blocks first == - combat = ComputerUtilBlock.makeGoodBlocks(ai, combat); - combat = ComputerUtilBlock.makeGangBlocks(ai, combat); + makeGoodBlocks(combat); + makeGangBlocks(combat); if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - combat = ComputerUtilBlock.makeTradeBlocks(ai, combat); // choose necessary trade blocks + makeTradeBlocks(combat); // choose necessary trade blocks } // if life is in danger if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - combat = ComputerUtilBlock.makeChumpBlocks(ai, combat); // choose necessary chump blocks + makeChumpBlocks(combat); // choose necessary chump blocks } // if life is still in danger // Reinforce blockers blocking attackers with trample if life is still // in danger if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - combat = ComputerUtilBlock.reinforceBlockersAgainstTrample(ai, combat); + reinforceBlockersAgainstTrample(combat); } // Support blockers not destroying the attacker with more blockers to // try to kill the attacker if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { - combat = ComputerUtilBlock.reinforceBlockersToKill(ai, combat); + reinforceBlockersToKill(combat); } - // == 2. If the AI life would still be in danger make a safer approach - // == + // == 2. If the AI life would still be in danger make a safer approach == if (ComputerUtilCombat.lifeInDanger(ai, combat)) { lifeInDanger = true; - combat = ComputerUtilBlock.resetBlockers(combat, possibleBlockers); // reset every block assignment - combat = ComputerUtilBlock.makeTradeBlocks(ai, combat); // choose necessary trade blocks + clearBlockers(combat, possibleBlockers); // reset every block assignment + makeTradeBlocks(combat); // choose necessary trade blocks // if life is in danger - combat = ComputerUtilBlock.makeGoodBlocks(ai, combat); + makeGoodBlocks(combat); // choose necessary chump blocks if life is still in danger if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - combat = ComputerUtilBlock.makeChumpBlocks(ai, combat); + makeChumpBlocks(combat); } // Reinforce blockers blocking attackers with trample if life is // still in danger if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - combat = ComputerUtilBlock.reinforceBlockersAgainstTrample(ai, combat); + reinforceBlockersAgainstTrample(combat); } - combat = ComputerUtilBlock.makeGangBlocks(ai, combat); - combat = ComputerUtilBlock.reinforceBlockersToKill(ai, combat); + makeGangBlocks(combat); + reinforceBlockersToKill(combat); } // == 3. If the AI life would be in serious danger make an even safer approach == if (lifeInDanger && ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) { - combat = ComputerUtilBlock.resetBlockers(combat, possibleBlockers); // reset every block assignment - combat = ComputerUtilBlock.makeChumpBlocks(ai, combat); // choose chump blocks + clearBlockers(combat, possibleBlockers); // reset every block assignment + makeChumpBlocks(combat); // choose chump blocks if (ComputerUtilCombat.lifeInDanger(ai, combat)) { - combat = ComputerUtilBlock.makeTradeBlocks(ai, combat); // choose necessary trade + makeTradeBlocks(combat); // choose necessary trade } if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { - combat = ComputerUtilBlock.makeGoodBlocks(ai, combat); + makeGoodBlocks(combat); } // Reinforce blockers blocking attackers with trample if life is // still in danger else { - combat = ComputerUtilBlock.reinforceBlockersAgainstTrample(ai, combat); + reinforceBlockersAgainstTrample(combat); } - combat = ComputerUtilBlock.makeGangBlocks(ai, combat); + makeGangBlocks(combat); // Support blockers not destroying the attacker with more blockers // to try to kill the attacker - combat = ComputerUtilBlock.reinforceBlockersToKill(ai, combat); + reinforceBlockersToKill(combat); } // assign blockers that have to block - chumpBlockers = CardLists.getKeyword(ComputerUtilBlock.getBlockersLeft(), "CARDNAME blocks each turn if able."); + chumpBlockers = CardLists.getKeyword(blockersLeft, "CARDNAME blocks each turn if able."); // if an attacker with lure attacks - all that can block - for (final Card blocker : ComputerUtilBlock.getBlockersLeft()) { + for (final Card blocker : blockersLeft) { if (CombatUtil.mustBlockAnAttacker(blocker, combat)) { chumpBlockers.add(blocker); } } if (!chumpBlockers.isEmpty()) { - CardLists.shuffle(ComputerUtilBlock.getAttackers()); - for (final Card attacker : ComputerUtilBlock.getAttackers()) { - blockers = ComputerUtilBlock.getPossibleBlockers(attacker, chumpBlockers, combat, false); + CardLists.shuffle(attackers); + for (final Card attacker : attackers) { + blockers = getPossibleBlockers(combat, attacker, chumpBlockers, false); for (final Card blocker : blockers) { - if (CombatUtil.canBlock(attacker, blocker, combat) && ComputerUtilBlock.getBlockersLeft().contains(blocker) + if (CombatUtil.canBlock(attacker, blocker, combat) && blockersLeft.contains(blocker) && (CombatUtil.mustBlockAnAttacker(blocker, combat) || blocker.hasKeyword("CARDNAME blocks each turn if able."))) { combat.addBlocker(attacker, blocker); - ComputerUtilBlock.getBlockersLeft().remove(blocker); + blockersLeft.remove(blocker); } } } } - - return combat; } public static List orderBlockers(Card attacker, List blockers) { diff --git a/src/main/java/forge/game/ai/AiController.java b/src/main/java/forge/game/ai/AiController.java index 631db4a9882..008c2e364a1 100644 --- a/src/main/java/forge/game/ai/AiController.java +++ b/src/main/java/forge/game/ai/AiController.java @@ -17,7 +17,6 @@ */ package forge.game.ai; -import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -46,7 +45,7 @@ import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellPermanent; import forge.game.GameActionUtil; import forge.game.Game; -import forge.game.phase.CombatUtil; +import forge.game.phase.Combat; import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; @@ -647,7 +646,7 @@ public class AiController { } String exMsg = String.format("AI confirmAction does not know what to decide about %s mode (api is null).", mode); - throw new InvalidParameterException(exMsg); + throw new IllegalArgumentException(exMsg); } else return api.getAi().confirmAction(player, sa, mode, message); @@ -741,19 +740,20 @@ public class AiController { } } - public void declateBlockers(Player defender) { - final List blockers = defender.getCreaturesInPlay(); + // declares blockers for given defender in a given combat + public void declareBlockersFor(Player defender, Combat combat) { + AiBlockController block = new AiBlockController(defender); // When player != defender, AI should declare blockers for its benefit. - game.setCombat(ComputerUtilBlock.getBlockers(defender, game.getCombat(), blockers)); - CombatUtil.orderMultipleCombatants(game.getCombat()); + block.assignBlockers(combat); } - public void declareAttackers(Player attacker) { + public Combat declareAttackers(Player attacker, Combat combat) { // 12/2/10(sol) the decision making here has moved to getAttackers() - game.setCombat(new AiAttackController(attacker, attacker.getOpponent()).getAttackers()); + AiAttackController aiAtk = new AiAttackController(attacker); + aiAtk.declareAttackers(combat); - for (final Card element : game.getCombat().getAttackers()) { + for (final Card element : combat.getAttackers()) { // tapping of attackers happens after Propaganda is paid for final StringBuilder sb = new StringBuilder(); sb.append("Computer just assigned ").append(element.getName()).append(" as an attacker."); @@ -766,6 +766,7 @@ public class AiController { for (Player p : game.getPlayers()) { p.getController().autoPassCancel(); } + return combat; } private void playLands() { diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index b349cef7ebb..752ed9dc089 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -676,21 +676,6 @@ public class ComputerUtil { return returnList; } - /** - *

- * getBlockers. - *

- * - * @return a {@link forge.game.phase.Combat} object. - */ - public static Combat getBlockers(final Player ai) { - final List blockers = ai.getCardsIn(ZoneType.Battlefield); - - return ComputerUtilBlock.getBlockers(ai, ai.getGame().getCombat(), blockers); - } - - - /** *

* sacrificePermanents. @@ -993,17 +978,18 @@ public class ComputerUtil { boolean ret = true; if (source.getManaCost().countX() > 0) { // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available. + return ret; } else { // Otherwise, if life is possibly in danger, then this is fine. - Combat combat = new Combat(); - combat.initiatePossibleDefenders(ai); + Combat combat = new Combat(ai.getOpponent()); List attackers = ai.getOpponent().getCreaturesInPlay(); for (Card att : attackers) { if (CombatUtil.canAttackNextTurn(att)) { combat.addAttacker(att, att.getController().getOpponent()); } } - combat = ComputerUtilBlock.getBlockers(ai, combat, ai.getCreaturesInPlay()); + AiBlockController aiBlock = new AiBlockController(ai); + aiBlock.assignBlockers(combat); if (!ComputerUtilCombat.lifeInDanger(ai, combat)) { // Otherwise, return false. Do not play now. ret = false; diff --git a/src/main/java/forge/game/ai/ComputerUtilCard.java b/src/main/java/forge/game/ai/ComputerUtilCard.java index 11b006d102f..5beb40e81b1 100644 --- a/src/main/java/forge/game/ai/ComputerUtilCard.java +++ b/src/main/java/forge/game/ai/ComputerUtilCard.java @@ -28,6 +28,7 @@ import forge.card.spellability.SpellAbility; import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.DeckSection; +import forge.game.phase.Combat; import forge.game.player.Player; import forge.item.PaperCard; import forge.util.Aggregates; @@ -614,9 +615,10 @@ public class ComputerUtilCard { * @return a boolean. */ public static boolean doesCreatureAttackAI(final Player ai, final Card card) { - final List att = new AiAttackController(ai, ai.getOpponent()).getAttackers().getAttackers(); - - return att.contains(card); + AiAttackController aiAtk = new AiAttackController(ai); + Combat combat = new Combat(ai); + aiAtk.declareAttackers(combat); + return combat.isAttacking(card); } /** diff --git a/src/main/java/forge/game/ai/ComputerUtilCombat.java b/src/main/java/forge/game/ai/ComputerUtilCombat.java index 15ae940127b..8a5302bc756 100644 --- a/src/main/java/forge/game/ai/ComputerUtilCombat.java +++ b/src/main/java/forge/game/ai/ComputerUtilCombat.java @@ -527,13 +527,13 @@ public class ComputerUtilCombat { * a {@link forge.Card} object. * @return a boolean. */ - public static boolean combatantWouldBeDestroyed(Player ai, final Card combatant) { + public static boolean combatantWouldBeDestroyed(Player ai, final Card combatant, Combat combat) { - if (combatant.isAttacking()) { - return ComputerUtilCombat.attackerWouldBeDestroyed(ai, combatant); + if (combat.isAttacking(combatant)) { + return ComputerUtilCombat.attackerWouldBeDestroyed(ai, combatant, combat); } - if (combatant.isBlocking()) { - return ComputerUtilCombat.blockerWouldBeDestroyed(ai, combatant); + if (combat.isBlocking(combatant)) { + return ComputerUtilCombat.blockerWouldBeDestroyed(ai, combatant, combat); } return false; } @@ -549,13 +549,11 @@ public class ComputerUtilCombat { * a {@link forge.Card} object. * @return a boolean. */ - public static boolean attackerWouldBeDestroyed(Player ai, final Card attacker) { - final Game game = ai.getGame(); - final Combat combat = game.getCombat(); + public static boolean attackerWouldBeDestroyed(Player ai, final Card attacker, Combat combat) { final List blockers = combat.getBlockers(attacker); for (final Card defender : blockers) { - if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, game.getCombat(), true) + if (ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, true) && !(defender.hasKeyword("Wither") || defender.hasKeyword("Infect"))) { return true; } @@ -600,7 +598,7 @@ public class ComputerUtilCombat { TriggerType mode = trigger.getMode(); if (mode == TriggerType.Attacks) { willTrigger = true; - if (attacker.isAttacking()) { + if (combat.isAttacking(attacker)) { return false; // The trigger should have triggered already } if (trigParams.containsKey("ValidCard")) { @@ -1510,14 +1508,13 @@ public class ComputerUtilCombat { * a {@link forge.Card} object. * @return a boolean. */ - public static boolean blockerWouldBeDestroyed(Player ai, final Card blocker) { - final Game game = ai.getGame(); + public static boolean blockerWouldBeDestroyed(Player ai, final Card blocker, Combat combat) { // TODO THis function only checks if a single attacker at a time would destroy a blocker // This needs to expand to tally up damage - final List attackers = game.getCombat().getAttackersBlockedBy(blocker); + final List attackers = combat.getAttackersBlockedBy(blocker); for (Card attacker : attackers) { - if (ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, game.getCombat(), true) + if (ComputerUtilCombat.canDestroyBlocker(ai, blocker, attacker, combat, true) && !(attacker.hasKeyword("Wither") || attacker.hasKeyword("Infect"))) { return true; } diff --git a/src/main/java/forge/game/combat/AttackingBand.java b/src/main/java/forge/game/combat/AttackingBand.java index 49667acad31..59869bf1832 100644 --- a/src/main/java/forge/game/combat/AttackingBand.java +++ b/src/main/java/forge/game/combat/AttackingBand.java @@ -11,40 +11,27 @@ import forge.GameEntity; * TODO: Write javadoc for this type. * */ -public class AttackingBand implements Comparable { - +public class AttackingBand { private List attackers = new ArrayList(); - private List blockers = new ArrayList(); - private GameEntity defender = null; - private Boolean blocked = null; +// private GameEntity defender = null; + private boolean blocked = false; // even if all blockers were killed before FS or CD, band remains blocked public AttackingBand(List band, GameEntity def) { attackers.addAll(band); - this.defender = def; +// this.defender = def; } public AttackingBand(Card card, GameEntity def) { attackers.add(card); - this.defender = def; +// this.defender = def; } public List getAttackers() { return this.attackers; } - public List getBlockers() { return this.blockers; } - public GameEntity getDefender() { return this.defender; } +// public GameEntity getDefender() { return this.defender; } public void addAttacker(Card card) { attackers.add(card); } public void removeAttacker(Card card) { attackers.remove(card); } - public void addBlocker(Card card) { blockers.add(card); } - public void removeBlocker(Card card) { blockers.remove(card); } - public void setBlockers(List blockers) { this.blockers = blockers; } - - public void setDefender(GameEntity def) { this.defender = def; } - - public void setBlocked(boolean blocked) { this.blocked = blocked; } - public boolean getBlocked() { return this.blocked != null && this.blocked.booleanValue(); } - - public void calculateBlockedState() { this.blocked = !this.blockers.isEmpty(); } public static boolean isValidBand(List band, boolean shareDamage) { if (band.isEmpty()) { @@ -92,26 +79,29 @@ public class AttackingBand implements Comparable { return isValidBand(newBand, false); } + + public boolean contains(Card c) { + return attackers.contains(c); + } + + public boolean isBlocked() { return blocked; } + public void setBlocked(boolean value) { blocked = value; } + + /** + * TODO: Write javadoc for this method. + * @return + */ + public boolean isEmpty() { + // TODO Auto-generated method stub + return attackers.isEmpty(); + } + /* (non-Javadoc) - * @see java.lang.Comparable#compareTo(java.lang.Object) + * @see java.lang.Object#toString() */ @Override - public int compareTo(AttackingBand o) { - if (o == null) { - return -1; - } - - List compareAttackers = o.getAttackers(); - - int sizeDiff = this.attackers.size() - compareAttackers.size(); - if (sizeDiff > 0) { - return 1; - } else if (sizeDiff < 0) { - return -1; - } else if (sizeDiff == 0 && this.attackers.isEmpty()) { - return 0; - } - - return this.attackers.get(0).compareTo(compareAttackers.get(0)); + public String toString() { + return String.format("%s %s", attackers.toString(), blocked ? ">||" : ">>>" ); } + } diff --git a/src/main/java/forge/game/phase/Combat.java b/src/main/java/forge/game/phase/Combat.java index e97d92b6cb1..4e28ba52d69 100644 --- a/src/main/java/forge/game/phase/Combat.java +++ b/src/main/java/forge/game/phase/Combat.java @@ -18,12 +18,11 @@ package forge.game.phase; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.TreeMap; - import com.google.common.collect.Lists; import forge.Card; @@ -32,9 +31,11 @@ import forge.CardPredicates; import forge.GameEntity; import forge.card.trigger.TriggerType; import forge.game.combat.AttackingBand; -import forge.game.event.GameEventBlockerAssigned; import forge.game.player.Player; import forge.game.zone.ZoneType; +import forge.util.maps.CollectionSuppliers; +import forge.util.maps.HashMapOfLists; +import forge.util.maps.MapOfLists; /** *

@@ -45,166 +46,69 @@ import forge.game.zone.ZoneType; * @version $Id$ */ public class Combat { - // List of AttackingBands - private final List attackingBands = new ArrayList(); - // Attacker -> AttackingBand (Attackers can only be in a single band) - private final Map attackerToBandMap = new TreeMap(); - // Blocker -> AttackingBands (Blockers can block multiple bands/creatures - private final Map> blockerToBandsMap = new TreeMap>(); + private final Player attackerPlayer; + // Defenders, as they are attacked by hostile forces + private final MapOfLists attackedEntities = new HashMapOfLists(CollectionSuppliers.arrayLists()); + // Blockers to stop the hostile invaders + private final MapOfLists blockedBands = new HashMapOfLists(CollectionSuppliers.arrayLists()); private final HashMap defendingDamageMap = new HashMap(); - - // Defenders are all Opposing Players + Planeswalker's Controller By Opposing Players - private Map> defenderMap = new HashMap>(); - private Map> blockerDamageAssignmentOrder = new TreeMap>(); - private Map> attackerDamageAssignmentOrder = new TreeMap>(); + + private Map> orderBlockerDamageAssignment = new HashMap>(); + private Map> orderAttackerDamageAssignment = new HashMap>(); - private Player attackingPlayer = null; - /** - *

- * reset. - *

- */ - public final void reset(Player playerTurn) { - this.resetAttackers(); - this.defendingDamageMap.clear(); - this.attackingPlayer = playerTurn; + public Combat(Player attacker) { + attackerPlayer = attacker; - this.initiatePossibleDefenders(playerTurn.getOpponents()); - } - - /** - *

- * initiatePossibleDefenders. - *

- * - * @param defender - * a {@link forge.game.player.Player} object. - */ - public final void initiatePossibleDefenders(final Iterable defenders) { - this.defenderMap.clear(); - for (Player defender : defenders) { - fillDefenderMaps(defender); + // Create keys for all possible attack targets + for (Player defender : attackerPlayer.getOpponents()) { + this.attackedEntities.ensureCollectionFor(defender); + List planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); + for (final Card pw : planeswalkers) { + this.attackedEntities.ensureCollectionFor(pw); + } } } - - public final void initiatePossibleDefenders(final Player defender) { - this.defenderMap.clear(); - fillDefenderMaps(defender); - } + public final Player getAttackingPlayer() { + return this.attackerPlayer; + } + public final boolean isCombat() { - return !attackingBands.isEmpty(); - } - - private void fillDefenderMaps(final Player defender) { - this.defenderMap.put(defender, new ArrayList()); - List planeswalkers = - CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); - for (final Card pw : planeswalkers) { - this.defenderMap.put(pw, new ArrayList()); + for(Collection abs : attackedEntities.values()) { + if(!abs.isEmpty()) + return true; } + return false; } - /** - *

- * Getter for the field defenders. - *

- * - * @return a {@link java.util.ArrayList} object. - */ public final List getDefenders() { - return new ArrayList(this.defenderMap.keySet()); + return Lists.newArrayList(attackedEntities.keySet()); } - /** - *

- * Setter for the field defenders. - *

- * - * @param newDef - * a {@link java.util.ArrayList} object. - */ - public final void setDefenders(final List newDef) { - this.defenderMap.clear(); - for (GameEntity entity : newDef) { - this.defenderMap.put(entity, new ArrayList()); - } - } - - /** - *

- * getDefendingPlaneswalkers. - *

- * - * @return an array of {@link forge.Card} objects. - */ public final List getDefendingPlayers() { final List defending = new ArrayList(); - - for (final GameEntity o : this.defenderMap.keySet()) { + for (final GameEntity o : attackedEntities.keySet()) { if (o instanceof Player) { defending.add((Player) o); } } - return defending; } - /** - *

- * getDefendingPlaneswalkers. - *

- * - * @return an array of {@link forge.Card} objects. - */ public final List getDefendingPlaneswalkers() { final List pwDefending = new ArrayList(); - - for (final GameEntity o : this.defenderMap.keySet()) { + for (final GameEntity o : attackedEntities.keySet()) { if (o instanceof Card) { pwDefending.add((Card) o); } } - return pwDefending; } - /** - *

- * Setter for the field attackingPlayer. - *

- * - * @param player - * a {@link forge.game.player.Player} object. - */ - public final void setAttackingPlayer(final Player player) { - this.attackingPlayer = player; - } - - /** - *

- * Getter for the field attackingPlayer. - *

- * - * @return a {@link forge.game.player.Player} object. - */ - public final Player getAttackingPlayer() { - return this.attackingPlayer; - } - - /** - *

- * addDefendingDamage. - *

- * - * @param n - * a int. - * @param source - * a {@link forge.Card} object. - */ + // Damage to whatever was protected there. public final void addDefendingDamage(final int n, final Card source) { final GameEntity ge = this.getDefenderByAttacker(source); @@ -222,80 +126,59 @@ public class Combat { } } - public final List getAttackersOf(GameEntity defender) { - return defenderMap.get(defender); + public final List getAttackingBandsOf(GameEntity defender) { + return Lists.newArrayList(attackedEntities.get(defender)); } - public final List getAttackingBandsOf(GameEntity defender) { - List bands = new ArrayList(); - for(AttackingBand band : this.attackingBands) { - if (band.getDefender().equals(defender)) { - bands.add(band); - } + public final List getAttackersOf(GameEntity defender) { + List result = new ArrayList(); + for(AttackingBand v : attackedEntities.get(defender)) { + result.addAll(v.getAttackers()); } - return bands; + return result; } - /** - *

- * isAttacking. - *

- * - * @param c - * a {@link forge.Card} object. - * @return a boolean. - */ - public final boolean isAttacking(final Card c) { - return this.attackerToBandMap.containsKey(c); - } public final void addAttacker(final Card c, GameEntity defender) { addAttacker(c, defender, null); } public final void addAttacker(final Card c, GameEntity defender, AttackingBand band) { - addAttacker(c, defender, band, null); - } - - public final void addAttacker(final Card c, GameEntity defender, boolean blocked) { - addAttacker(c, defender, null, blocked); - } - - public final void addAttacker(final Card c, GameEntity defender, AttackingBand band, Boolean blocked) { - if (!defenderMap.containsKey(defender)) { + Collection attackersOfDefender = attackedEntities.get(defender); + if (attackersOfDefender == null) { System.out.println("Trying to add Attacker " + c + " to missing defender " + defender); return; } - if (band == null || !this.attackingBands.contains(band)) { + + + if (band == null || !attackersOfDefender.contains(band)) { band = new AttackingBand(c, defender); - if (blocked != null) { - band.setBlocked(blocked.booleanValue()); - } - this.attackingBands.add(band); + attackersOfDefender.add(band); } else { band.addAttacker(c); } - // Attacker -> Defender and Defender -> Attacker map to Bands? - this.defenderMap.get(defender).add(c); - this.attackerToBandMap.put(c, band); } - /** - *

- * getDefenderByAttacker. - *

- * - * @param c - * a {@link forge.Card} object. - * @return a {@link java.lang.Object} object. - */ public final GameEntity getDefenderByAttacker(final Card c) { - if (!this.attackerToBandMap.containsKey(c)) { - return null; + for(Entry> e : attackedEntities.entrySet()) { + for(AttackingBand ab : e.getValue()) { + if ( ab.contains(c) ) + return e.getKey(); + } } - return this.attackerToBandMap.get(c).getDefender(); + return null; } + + private final GameEntity getDefenderByAttacker(final AttackingBand c) { + for(Entry> e : attackedEntities.entrySet()) { + for(AttackingBand ab : e.getValue()) { + if ( ab == c ) + return e.getKey(); + } + } + return null; + } public final Player getDefenderPlayerByAttacker(final Card c) { GameEntity defender = getDefenderByAttacker(c); @@ -312,181 +195,112 @@ public class Combat { return null; } - public final GameEntity getDefendingEntity(final Card c) { - GameEntity defender = this.getDefenderByAttacker(c); - if (this.defenderMap.containsKey(defender)) { - return defender; + public final AttackingBand getBandOfAttacker(final Card c) { + for(Collection abs : attackedEntities.values()) { + for(AttackingBand ab : abs) { + if ( ab.contains(c) ) + return ab; + } } - - System.out.println("Attacker " + c + " missing defender " + defender); return null; } - public final AttackingBand getBandByAttacker(final Card c) { - return this.attackerToBandMap.get(c); - } - - /** - *

- * resetAttackers. - *

- */ - public final void resetAttackers() { - this.attackingBands.clear(); - this.blockerToBandsMap.clear(); - this.attackerToBandMap.clear(); - this.blockerDamageAssignmentOrder.clear(); - this.attackerDamageAssignmentOrder.clear(); - } - - /** - *

- * getAttackers. - *

- * - * @return an array of {@link forge.Card} objects. - */ public final List getAttackingBands() { - return attackingBands; - } // getAttackers() + List result = Lists.newArrayList(); + for(Collection abs : attackedEntities.values()) + result.addAll(abs); + return result; + } + public final boolean isAttacking(final Card c) { + AttackingBand ab = getBandOfAttacker(c); + return ab != null; + } + + /** + * TODO: Write javadoc for this method. + * @param card + * @param currentDefender + * @return + */ + public boolean isAttacking(Card card, GameEntity defender) { + for(Entry> ee : attackedEntities.entrySet()) + for(AttackingBand ab : ee.getValue()) + if (ab.contains(card)) + return ee.getKey() == defender; + return false; + } + public final List getAttackers() { - List attackers = new ArrayList(); - for(AttackingBand band : attackingBands) { - attackers.addAll(band.getAttackers()); - } - return attackers; - } - - public final boolean isBlocked(final Card attacker) { - return this.attackerToBandMap.get(attacker).getBlocked(); - } - - public final void setBlocked(final Card attacker) { - this.attackerToBandMap.get(attacker).setBlocked(true); - } - - /** - *

- * addBlocker. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @param blocker - * a {@link forge.Card} object. - */ - public final void addBlocker(final Card attacker, final Card blocker) { - AttackingBand band = this.attackerToBandMap.get(attacker); - band.addBlocker(blocker); - - if (!this.blockerToBandsMap.containsKey(blocker)) { - this.blockerToBandsMap.put(blocker, Lists.newArrayList(band)); - } else { - this.blockerToBandsMap.get(blocker).add(band); - } - attacker.getGame().fireEvent(new GameEventBlockerAssigned()); - } - - public final void removeBlockAssignment(final Card attacker, final Card blocker) { - AttackingBand band = this.attackerToBandMap.get(attacker); - band.removeBlocker(blocker); - this.blockerToBandsMap.get(blocker).remove(attacker); - if (this.blockerToBandsMap.get(blocker).isEmpty()) { - this.blockerToBandsMap.remove(blocker); - } - } - - /** - *

- * undoBlockingAssignment. - *

- * - * @param blocker - * a {@link forge.Card} object. - */ - public final void undoBlockingAssignment(final Card blocker) { - final List att = this.blockerToBandsMap.get(blocker); - for (final AttackingBand band : att) { - band.removeBlocker(blocker); - } - this.blockerToBandsMap.remove(blocker); - } - - /** - *

- * getAllBlockers. - *

- * - * @return a {@link forge.CardList} object. - */ - public final List getAllBlockers() { - final List block = new ArrayList(); - block.addAll(blockerToBandsMap.keySet()); - - return block; - } - - public final List getBlockers(final AttackingBand band) { - List list = band.getBlockers(); - if (list == null) { - return new ArrayList(); - } else { - return new ArrayList(list); - } + List result = Lists.newArrayList(); + for(Collection abs : attackedEntities.values()) + for(AttackingBand ab : abs) + result.addAll(ab.getAttackers()); + return result; } public final List getBlockers(final Card card) { - return getBlockers(card, false); - } - - public final List getBlockers(final Card card, boolean ordered) { // If requesting the ordered blocking list pass true, directly. - List list = null; - if (ordered) { - list = this.attackerDamageAssignmentOrder.containsKey(card) ? this.attackerDamageAssignmentOrder.get(card) : null; - } else { - list = this.attackerToBandMap.containsKey(card) ? this.getBandByAttacker(card).getBlockers() : null; - } + AttackingBand band = getBandOfAttacker(card); + Collection blockers = blockedBands.get(band); + return blockers == null ? Lists.newArrayList() : Lists.newArrayList(blockers); + } - if (list == null) { - return new ArrayList(); - } else { - return new ArrayList(list); + public final boolean isBlocked(final Card attacker) { + AttackingBand band = getBandOfAttacker(attacker); + return band == null ? false : band.isBlocked(); + + } + + public final void setBlocked(final Card attacker, boolean value) { + getBandOfAttacker(attacker).setBlocked(value); // called by Curtain of Light, Dazzling Beauty, Trap Runner + } + + public final void addBlocker(final Card attacker, final Card blocker) { + AttackingBand band = getBandOfAttacker(attacker); + blockedBands.add(band, blocker); + } + + // remove blocked from specific attacker + public final void removeBlockAssignment(final Card attacker, final Card blocker) { + AttackingBand band = getBandOfAttacker(attacker); + Collection cc = blockedBands.get(band); + if( cc != null) + cc.remove(blocker); + } + + // remove blocker from everywhere + public final void undoBlockingAssignment(final Card blocker) { + for(Collection blockers : blockedBands.values()) { + blockers.remove(blocker); } } - /** - *

- * getAttackerBlockedBy. - *

- * - * @param blocker - * a {@link forge.Card} object. - * @return a {@link forge.Card} object. - */ + public final List getAllBlockers() { + List result = new ArrayList(); + for(Collection blockers : blockedBands.values()) { + if(!result.contains(blockers)) + result.addAll(blockers); + } + return result; + } + + public final List getBlockers(final AttackingBand band) { + Collection blockers = blockedBands.get(band); + return blockers == null ? Lists.newArrayList() : Lists.newArrayList(blockers); + } + + public final List getAttackersBlockedBy(final Card blocker) { List blocked = new ArrayList(); - - if (blockerToBandsMap.containsKey(blocker)) { - for(AttackingBand band : blockerToBandsMap.get(blocker)) { - blocked.addAll(band.getAttackers()); - } + for(Entry> s : blockedBands.entrySet()) { + if (s.getValue().contains(blocker)) + blocked.addAll(s.getKey().getAttackers()); } return blocked; } - /** - *

- * getDefendingPlayer. - *

- * - * @param attacker - * a {@link forge.Card} object. - * @return a {@link forge.Player} object. - */ - public List getDefendingPlayerRelatedTo(final Card source) { - List players = new ArrayList(); + public Player getDefendingPlayerRelatedTo(final Card source) { Card attacker = source; if (source.isAura()) { attacker = source.getEnchantingCard(); @@ -497,109 +311,107 @@ public class Combat { } // return the corresponding defender - Player defender = getDefenderPlayerByAttacker(attacker); - if (null != defender) { - players.add(defender); - return players; - } - - // Can't figure out who it's related to... just return all??? - // return all defending players - List defenders = this.getDefenders(); - for (GameEntity ge : defenders) { - if (ge instanceof Player) { - players.add((Player) ge); - } - } - return players; + return getDefenderPlayerByAttacker(attacker); } public void setAttackerDamageAssignmentOrder(final Card attacker, final List blockers) { - this.attackerDamageAssignmentOrder.put(attacker, blockers); + this.orderAttackerDamageAssignment.put(attacker, blockers); } public void setBlockerDamageAssignmentOrder(final Card blocker, final List attackers) { - this.blockerDamageAssignmentOrder.put(blocker, attackers); + this.orderBlockerDamageAssignment.put(blocker, attackers); } - public final void removeFromCombat(final Card c) { - // is card an attacker? - if (this.attackerToBandMap.containsKey(c)) { - // Soooo many maps to keep track of - AttackingBand band = this.attackerToBandMap.get(c); - band.removeAttacker(c); - this.attackerToBandMap.remove(c); - this.attackerDamageAssignmentOrder.remove(c); - - List blockers = band.getBlockers(); + // removes references to this attacker from all indices and orders + private void unregisterAttacker(final Card c, AttackingBand ab) { + orderAttackerDamageAssignment.remove(c); + + Collection blockers = blockedBands.get(ab); + if ( blockers != null ) { for (Card b : blockers) { - if (band.getAttackers().isEmpty()) { - this.blockerToBandsMap.get(b).remove(c); - } - // Clear removed attacker from assignment order - if (this.blockerDamageAssignmentOrder.containsKey(b)) { - this.blockerDamageAssignmentOrder.get(b).remove(c); + // Clear removed attacker from assignment order + if (this.orderBlockerDamageAssignment.containsKey(b)) { + this.orderBlockerDamageAssignment.get(b).remove(c); } } + } + return; + } - this.defenderMap.get(band.getDefender()).remove(c); - - if (band.getAttackers().isEmpty() && band.getBlockers().isEmpty()) { - this.getAttackingBands().remove(band); + // removes references to this defender from all indices and orders + private void unregisterDefender(final Card c, AttackingBand bandBeingBlocked) { + this.orderBlockerDamageAssignment.remove(c); + for(Card atk : bandBeingBlocked.getAttackers()) { + if (this.orderAttackerDamageAssignment.containsKey(atk)) { + this.orderAttackerDamageAssignment.get(atk).remove(c); } - } else if (this.blockerToBandsMap.containsKey(c)) { // card is a blocker - List attackers = this.blockerToBandsMap.get(c); + } + } - this.blockerToBandsMap.remove(c); - this.blockerDamageAssignmentOrder.remove(c); - for (AttackingBand a : attackers) { - a.removeBlocker(c); - for(Card atk : a.getAttackers()) { - if (this.attackerDamageAssignmentOrder.containsKey(atk)) { - this.attackerDamageAssignmentOrder.get(atk).remove(c); - } - } + // remove a combatant whose side is unknown + public final void removeFromCombat(final Card c) { + AttackingBand ab = getBandOfAttacker(c); + if (ab != null) { + unregisterAttacker(c, ab); + ab.removeAttacker(c); + // no need to remove empty bands + } + + // if not found in attackers, look for this card in blockers + for(Entry> be : blockedBands.entrySet()) { + Collection blockers = be.getValue(); + if(blockers.contains(c)) { + unregisterDefender(c, be.getKey()); + blockers.remove(c); } } } // removeFromCombat() - /** - *

- * verifyCreaturesInPlay. - *

- */ public final void removeAbsentCombatants() { - final List all = new ArrayList(); - for(AttackingBand band : this.getAttackingBands()) { - all.addAll(band.getAttackers()); - } - all.addAll(this.getAllBlockers()); - - for (int i = 0; i < all.size(); i++) { - if (!all.get(i).isInPlay()) { - this.removeFromCombat(all.get(i)); + // iterate all attackers and remove them + for(Entry> ee : attackedEntities.entrySet()) { + for(AttackingBand ab : ee.getValue()) { + List atk = ab.getAttackers(); + for(int i = atk.size() - 1; i >= 0; i--) { // might remove items from collection, so no iterators + Card c = atk.get(i); + if ( !c.isInPlay() ) { + unregisterAttacker(c, ab); + ab.removeAttacker(c); + } + } } } + + Collection toRemove = Lists.newArrayList(); + for(Entry> be : blockedBands.entrySet()) { + toRemove.clear(); + for( Card b : be.getValue()) { + if ( !b.isInPlay() ) { + unregisterDefender(b, be.getKey()); + toRemove.add(b); + } + } + be.getValue().removeAll(toRemove); + } } // verifyCreaturesInPlay() - /** - *

- * setUnblocked. - *

- */ - public final void setUnblockedAttackers() { - final List attacking = this.getAttackingBands(); - for (final AttackingBand band : attacking) { - band.calculateBlockedState(); - - if (!band.getBlocked()) { - for (Card attacker : band.getAttackers()) { - // Run Unblocked Trigger - final HashMap runParams = new HashMap(); - runParams.put("Attacker", attacker); - runParams.put("Defender",this.getDefenderByAttacker(attacker)); - attacker.getGame().getTriggerHandler().runTrigger(TriggerType.AttackerUnblocked, runParams, false); - } + + // Call this method right after turn-based action of declare blockers has been performed + public final void onBlockersDeclared() { + for(Collection abs : attackedEntities.values()) { + for(AttackingBand ab : abs) { + Collection blockers = blockedBands.get(ab); + boolean isBlocked = blockers != null && !blockers.isEmpty(); + if (isBlocked) + ab.setBlocked(true); + else + for (Card attacker : ab.getAttackers()) { + // Run Unblocked Trigger + final HashMap runParams = new HashMap(); + runParams.put("Attacker", attacker); + runParams.put("Defender",this.getDefenderByAttacker(attacker)); + attacker.getGame().getTriggerHandler().runTrigger(TriggerType.AttackerUnblocked, runParams, false); + } } } } @@ -611,7 +423,7 @@ public class Combat { for (final Card blocker : blockers) { if (blocker.hasDoubleStrike() || blocker.hasFirstStrike() == firstStrikeDamage) { - List attackers = this.blockerDamageAssignmentOrder.get(blocker); + List attackers = this.orderBlockerDamageAssignment.get(blocker); final int damage = blocker.getNetCombatDamage(); @@ -619,9 +431,8 @@ public class Combat { Player attackingPlayer = this.getAttackingPlayer(); Player assigningPlayer = blocker.getController(); - if (AttackingBand.isValidBand(attackers, true)) { + if (AttackingBand.isValidBand(attackers, true)) assigningPlayer = attackingPlayer; - } assignedDamage = true; Map map = assigningPlayer.getController().assignCombatDamage(blocker, attackers, damage, null, assigningPlayer != blocker.getController()); @@ -654,27 +465,25 @@ public class Combat { continue; } - AttackingBand band = this.getBandByAttacker(attacker); + AttackingBand band = this.getBandOfAttacker(attacker); boolean trampler = attacker.hasKeyword("Trample"); - blockers = this.attackerDamageAssignmentOrder.get(attacker); + blockers = this.orderAttackerDamageAssignment.get(attacker); assignedDamage = true; // If the Attacker is unblocked, or it's a trampler and has 0 blockers, deal damage to defender if (blockers == null || blockers.isEmpty()) { - if (trampler || !band.getBlocked()) { + if (trampler || !band.isBlocked()) { this.addDefendingDamage(damageDealt, attacker); } // No damage happens if blocked but no blockers left } else { - GameEntity defender = band.getDefender(); + GameEntity defender = getDefenderByAttacker(band); Player assigningPlayer = this.getAttackingPlayer(); // Defensive Formation is very similar to Banding with Blockers // It allows the defending player to assign damage instead of the attacking player if (defender instanceof Player && defender.hasKeyword("You assign combat damage of each creature attacking you.")) { assigningPlayer = (Player)defender; - } else { - if (AttackingBand.isValidBand(blockers, true)) { - assigningPlayer = blockers.get(0).getController(); - } + } else if ( AttackingBand.isValidBand(blockers, true)){ + assigningPlayer = blockers.get(0).getController(); } Map map = assigningPlayer.getController().assignCombatDamage(attacker, blockers, damageDealt, defender, this.getAttackingPlayer() != assigningPlayer); @@ -711,7 +520,7 @@ public class Combat { final HashMap> wasDamaged = new HashMap>(); for (final Entry entry : defMap.entrySet()) { - GameEntity defender = getDefendingEntity(entry.getKey()); + GameEntity defender = getDefenderByAttacker(entry.getKey()); if (defender instanceof Player) { // player if (((Player) defender).addCombatDamage(entry.getValue(), entry.getKey())) { if (wasDamaged.containsKey(defender)) { @@ -787,7 +596,7 @@ public class Combat { * @return a boolean. */ public final boolean isUnblocked(final Card att) { - return !this.attackerToBandMap.get(att).getBlocked(); + return !isBlocked(att); } /** @@ -798,26 +607,42 @@ public class Combat { * @return an array of {@link forge.Card} objects. */ public final List getUnblockedAttackers() { - ArrayList unblocked = new ArrayList(); - for (AttackingBand band : this.attackingBands) { - if (!band.getBlocked()) { - unblocked.addAll(band.getAttackers()); - } - } + List unblocked = new ArrayList(); + for (Collection abs : attackedEntities.values()) + for (AttackingBand ab : abs) + if ( ab.isBlocked() ) + unblocked.addAll(ab.getAttackers()); return unblocked; } - public boolean isPlayerAttacked(Player priority) { - for(GameEntity defender : defenderMap.keySet()) { - if ((defender instanceof Player && priority.equals(defender)) || - (defender instanceof Card && priority.equals(((Card)defender).getController()))) { - List attackers = defenderMap.get(defender); - if (attackers != null && !attackers.isEmpty()) + public boolean isPlayerAttacked(Player who) { + for(Entry> ee : attackedEntities.entrySet() ) { + GameEntity defender = ee.getKey(); + Card defenderAsCard = defender instanceof Card ? (Card)defender : null; + if ((null != defenderAsCard && defenderAsCard.getController() != who ) || + (null == defenderAsCard && defender != who) ) + continue; // defender is not related to player 'who' + + for(AttackingBand ab : ee.getValue()) { + if ( !ab.isEmpty() ) return true; } } - return false; } + + public boolean isBlocking(Card blocker) { + for (Collection blockers : blockedBands.values()) + if (blockers.contains(blocker)) + return true; + return false; + } + + public boolean isBlocking(Card blocker, Card attacker) { + AttackingBand ab = getBandOfAttacker(attacker); + Collection blockers = blockedBands.get(ab); + return blockers != null && blockers.contains(blocker); + } + } // Class Combat diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index 7565ec181bb..1c31fea5215 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -176,7 +176,7 @@ public class CombatUtil { * a {@link forge.game.phase.Combat} object. * @return a boolean. */ - public static boolean canBeBlocked(final Card attacker, final Combat combat) { + public static boolean canBeBlocked(final Card attacker, final Combat combat, Player defendingPlayer) { if (attacker == null) { return true; @@ -185,7 +185,7 @@ public class CombatUtil { if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) { return false; } - return CombatUtil.canBeBlocked(attacker); + return CombatUtil.canBeBlocked(attacker, defendingPlayer); } // can the attacker be blocked at all? @@ -198,7 +198,7 @@ public class CombatUtil { * a {@link forge.Card} object. * @return a boolean. */ - public static boolean canBeBlocked(final Card attacker) { + public static boolean canBeBlocked(final Card attacker, Player defender) { if (attacker == null) { return true; @@ -209,7 +209,7 @@ public class CombatUtil { } // Landwalk - if (isUnblockableFromLandwalk(attacker)) { + if (isUnblockableFromLandwalk(attacker, defender)) { return false; } @@ -217,7 +217,7 @@ public class CombatUtil { } - public static boolean isUnblockableFromLandwalk(final Card attacker) { + public static boolean isUnblockableFromLandwalk(final Card attacker, Player defendingPlayer) { //May be blocked as though it doesn't have landwalk. (Staff of the Ages) if (attacker.hasKeyword("May be blocked as though it doesn't have landwalk.")) { return false; @@ -277,10 +277,6 @@ public class CombatUtil { } String valid = StringUtils.join(walkTypes, ","); - Player defendingPlayer = attacker.getController().getOpponent(); - if (attacker.isAttacking()) { - defendingPlayer = defendingPlayer.getGame().getCombat().getDefendingPlayerRelatedTo(attacker).get(0); - } List defendingLands = defendingPlayer.getCardsIn(ZoneType.Battlefield); for (Card c : defendingLands) { if (c.isValid(valid.split(","), defendingPlayer, attacker)) { @@ -319,13 +315,9 @@ public class CombatUtil { * @return true, if successful */ public static boolean canBeBlocked(final Card attacker, final List blockers) { - if (!CombatUtil.canBeBlocked(attacker)) { - return false; - } - int blocks = 0; for (final Card blocker : blockers) { - if (CombatUtil.canBlock(attacker, blocker)) { + if (CombatUtil.canBeBlocked(attacker, blocker.getController()) && CombatUtil.canBlock(attacker, blocker)) { blocks++; } } @@ -366,32 +358,33 @@ public class CombatUtil { * a {@link forge.game.phase.Combat} object. * @return a boolean. */ - public static boolean finishedMandatoryBlocks(final Combat combat, final Player defending) { + public static String validateBlocks(final Combat combat, final Player defending) { - final List blockers = defending.getCreaturesInPlay(); + final List defendersArmy = defending.getCreaturesInPlay(); final List attackers = combat.getAttackers(); + final List blockers = CardLists.filterControlledBy(combat.getAllBlockers(), defending); // if a creature does not block but should, return false - for (final Card blocker : blockers) { + for (final Card blocker : defendersArmy) { // lure effects - if (!combat.getAllBlockers().contains(blocker) && CombatUtil.mustBlockAnAttacker(blocker, combat)) { - return false; + if (!blockers.contains(blocker) && CombatUtil.mustBlockAnAttacker(blocker, combat)) { + return String.format("%s must block an attacker, but has not been assigned to block any.", blocker); } // "CARDNAME blocks each turn if able." - if (!combat.getAllBlockers().contains(blocker) && blocker.hasKeyword("CARDNAME blocks each turn if able.")) { + if (!blockers.contains(blocker) && blocker.hasKeyword("CARDNAME blocks each turn if able.")) { for (final Card attacker : attackers) { if (CombatUtil.canBlock(attacker, blocker, combat)) { boolean must = true; if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { - final List possibleBlockers = CardLists.filter(defending.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); + final List possibleBlockers = Lists.newArrayList(defendersArmy); possibleBlockers.remove(blocker); if (!CombatUtil.canBeBlocked(attacker, possibleBlockers)) { must = false; } } if (must) { - return false; + return String.format("%s must block each turn, but was not assigned to block any attacker now", blocker); } } } @@ -399,20 +392,16 @@ public class CombatUtil { } for (final Card attacker : attackers) { + int cntBlockers = combat.getBlockers(attacker).size(); // don't accept blocker amount for attackers with keyword defining valid blockers amount - if (!canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size())) - return false; + if (cntBlockers > 0 && !canAttackerBeBlockedWithAmount(attacker, cntBlockers)) + return String.format("%s cannot be blocked with %d creatures you've assigned", attacker, cntBlockers); } - return true; + return null; } - public static void orderMultipleCombatants(final Combat combat) { - CombatUtil.orderMultipleBlockers(combat); - CombatUtil.orderBlockingMultipleAttackers(combat); - } - - private static void orderMultipleBlockers(final Combat combat) { + public static void orderMultipleBlockers(final Combat combat) { // If there are multiple blockers, the Attacker declares the Assignment Order final Player player = combat.getAttackingPlayer(); for (final AttackingBand band : combat.getAttackingBands()) { @@ -435,7 +424,7 @@ public class CombatUtil { } } - private static void orderBlockingMultipleAttackers(final Combat combat) { + public static void orderBlockingMultipleAttackers(final Combat combat) { // If there are multiple blockers, the Attacker declares the Assignment Order for (final Card blocker : combat.getAllBlockers()) { List attackers = combat.getAttackersBlockedBy(blocker); @@ -485,8 +474,9 @@ public class CombatUtil { } } + final Player defender = blocker.getController(); for (final Card attacker : attackersWithLure) { - if (CombatUtil.canBeBlocked(attacker, combat) && CombatUtil.canBlock(attacker, blocker)) { + if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker)) { boolean canBe = true; if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { final List blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay(); @@ -503,7 +493,7 @@ public class CombatUtil { if (blocker.getMustBlockCards() != null) { for (final Card attacker : blocker.getMustBlockCards()) { - if (CombatUtil.canBeBlocked(attacker, combat) && CombatUtil.canBlock(attacker, blocker) + if (CombatUtil.canBeBlocked(attacker, combat, defender) && CombatUtil.canBlock(attacker, blocker) && combat.isAttacking(attacker)) { boolean canBe = true; if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) { @@ -546,7 +536,7 @@ public class CombatUtil { if (!CombatUtil.canBlock(blocker, combat)) { return false; } - if (!CombatUtil.canBeBlocked(attacker, combat)) { + if (!CombatUtil.canBeBlocked(attacker, combat, blocker.getController())) { return false; } @@ -600,7 +590,7 @@ public class CombatUtil { if (!CombatUtil.canBlock(blocker, nextTurn)) { return false; } - if (!CombatUtil.canBeBlocked(attacker)) { + if (!CombatUtil.canBeBlocked(attacker, blocker.getController())) { return false; } @@ -684,6 +674,18 @@ public class CombatUtil { return true; } // canBlock() + 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.")) { + if (combat.getAttackers().size() < 2) { + combat.removeFromCombat(c1); + } + } + } + } + // can a creature attack given the combat state /** *

@@ -762,6 +764,9 @@ public class CombatUtil { } public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount) { + if( amount == 0 ) + return false; // no block + List restrictions = Lists.newArrayList(); for ( String kw : attacker.getKeyword() ) { if ( kw.startsWith("CantBeBlockedByAmount") ) @@ -918,13 +923,13 @@ public class CombatUtil { * @param bLast * a boolean. */ - public static boolean checkPropagandaEffects(final Game game, final Card c) { + public static boolean checkPropagandaEffects(final Game game, final Card c, final Combat combat) { Cost attackCost = new Cost(ManaCost.ZERO, true); // Sort abilities to apply them in proper order for (Card card : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { final ArrayList staticAbilities = card.getStaticAbilities(); for (final StaticAbility stAb : staticAbilities) { - Cost additionalCost = stAb.getAttackCost(c, game.getCombat().getDefenderByAttacker(c)); + Cost additionalCost = stAb.getAttackCost(c, combat.getDefenderByAttacker(c)); if ( null != additionalCost ) attackCost.add(additionalCost); } @@ -944,14 +949,14 @@ public class CombatUtil { * @param c * a {@link forge.Card} object. */ - public static void checkDeclareAttackers(final Game game, final Card c) { + public static void checkDeclaredAttacker(final Game game, final Card c, final Combat combat) { // Run triggers final HashMap runParams = new HashMap(); runParams.put("Attacker", c); - final List otherAttackers = game.getCombat().getAttackers(); + final List otherAttackers = combat.getAttackers(); otherAttackers.remove(c); runParams.put("OtherAttackers", otherAttackers); - runParams.put("Attacked", game.getCombat().getDefenderByAttacker(c)); + runParams.put("Attacked", combat.getDefenderByAttacker(c)); game.getTriggerHandler().runTrigger(TriggerType.Attacks, runParams, false); // Annihilator: @@ -965,7 +970,7 @@ public class CombatUtil { @Override public void resolve() { this.api = ApiType.Sacrifice; - final Player opponent = game.getCombat().getDefendingPlayerRelatedTo(c).get(0); + final Player opponent = combat.getDefendingPlayerRelatedTo(c); //List list = AbilityUtils.filterListByType(opponent.getCardsIn(ZoneType.Battlefield), "Permanent", this); final List list = opponent.getCardsIn(ZoneType.Battlefield); List toSac = opponent.getController().choosePermanentsToSacrifice(this, a, a, list, "Card"); @@ -1037,7 +1042,7 @@ public class CombatUtil { * @param cl * a {@link forge.CardList} object. */ - public static void checkDeclareBlockers(Game game, final List cl) { + public static void checkDeclareBlockers(Game game, final List cl, Combat combat) { for (final Card c : cl) { if (!c.getDamageHistory().getCreatureBlockedThisCombat()) { for (final Ability ab : CardFactoryUtil.getBushidoEffects(c)) { @@ -1046,7 +1051,7 @@ public class CombatUtil { // Run triggers final HashMap runParams = new HashMap(); runParams.put("Blocker", c); - final Card attacker = game.getCombat().getAttackersBlockedBy(c).get(0); + final Card attacker = combat.getAttackersBlockedBy(c).get(0); runParams.put("Attacker", attacker); game.getTriggerHandler().runTrigger(TriggerType.Blocks, runParams, false); } @@ -1068,7 +1073,6 @@ public class CombatUtil { * a {@link forge.Card} object. */ public static void checkBlockedAttackers(final Game game, final Card a, final List blockers) { - final Combat combat = game.getCombat(); if (blockers.isEmpty()) { return; } @@ -1095,7 +1099,7 @@ public class CombatUtil { if (m.find()) { final String[] k = keyword.split(" "); final int magnitude = Integer.valueOf(k[1]); - final int numBlockers = combat.getBlockers(a).size(); + final int numBlockers = blockers.size(); if (numBlockers > 1) { CombatUtil.executeRampageAbility(game, a, magnitude, numBlockers); } @@ -1265,16 +1269,4 @@ 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 c43843dd520..3e02b3a1203 100644 --- a/src/main/java/forge/game/phase/PhaseHandler.java +++ b/src/main/java/forge/game/phase/PhaseHandler.java @@ -40,6 +40,7 @@ import forge.game.GameAge; import forge.game.Game; import forge.game.GameType; import forge.game.event.GameEventAttackersDeclared; +import forge.game.event.GameEventBlockerAssigned; import forge.game.event.GameEventBlockersDeclared; import forge.game.event.GameEventPlayerPriority; import forge.game.event.GameEventTurnBegan; @@ -91,7 +92,7 @@ public class PhaseHandler implements java.io.Serializable { private Player pPlayerPriority = null; private Player pFirstPriority = null; - private boolean bCombat = false; + private Combat combat = null; private boolean bRepeatCleanup = false; private Player playerDeclaresBlockers = null; @@ -184,9 +185,8 @@ public class PhaseHandler implements java.io.Serializable { * * @return a boolean. */ - public final boolean inCombat() { - return this.bCombat; - } + public final boolean inCombat() { return combat != null; } + public final Combat getCombat() { return this.combat; } private void advanceToNextPhase() { PhaseType oldPhase = phase; @@ -215,9 +215,6 @@ public class PhaseHandler implements java.io.Serializable { this.turn++; game.fireEvent(new GameEventTurnBegan(playerTurn, turn)); - // Here's what happens on new turn, regardless of skipped phases - game.getCombat().reset(playerTurn); - // Tokens starting game in play should suffer from Sum. Sickness final List list = playerTurn.getCardsIncludePhasingIn(ZoneType.Battlefield); for (final Card c : list) { @@ -305,43 +302,45 @@ public class PhaseHandler implements java.io.Serializable { break; case COMBAT_DECLARE_ATTACKERS: - this.bCombat = true; + combat = new Combat(playerTurn); game.getStack().freezeStack(); - declareAttackersTurnBasedActions(); + declareAttackersTurnBasedAction(); game.getStack().unfreezeStack(); + + if (combat != null && combat.getAttackers().isEmpty() ) + combat = null; - this.bCombat = !game.getCombat().getAttackers().isEmpty(); - givePriorityToPlayer = bCombat; + givePriorityToPlayer = inCombat(); this.nCombatsThisTurn++; break; case COMBAT_DECLARE_BLOCKERS: - game.getCombat().removeAbsentCombatants(); + combat.removeAbsentCombatants(); game.getStack().freezeStack(); - declareBlockersTurnBaseActions(); + declareBlockersTurnBasedAction(); game.getStack().unfreezeStack(); break; case COMBAT_FIRST_STRIKE_DAMAGE: - game.getCombat().removeAbsentCombatants(); + combat.removeAbsentCombatants(); // no first strikers, skip this step - if (!game.getCombat().assignCombatDamage(true)) { + if (!combat.assignCombatDamage(true)) { this.givePriorityToPlayer = false; } else { - game.getCombat().dealAssignedDamage(); + combat.dealAssignedDamage(); game.getAction().checkStateEffects(); } break; case COMBAT_DAMAGE: - game.getCombat().removeAbsentCombatants(); + combat.removeAbsentCombatants(); - if (!game.getCombat().assignCombatDamage(false)) { + if (!combat.assignCombatDamage(false)) { this.givePriorityToPlayer = false; } else { - game.getCombat().dealAssignedDamage(); + combat.dealAssignedDamage(); game.getAction().checkStateEffects(); } break; @@ -445,9 +444,8 @@ public class PhaseHandler implements java.io.Serializable { break; case COMBAT_END: - game.getCombat().reset(playerTurn); + combat = null; this.getPlayerTurn().resetAttackedThisCombat(); - this.bCombat = false; break; case CLEANUP: @@ -463,71 +461,75 @@ public class PhaseHandler implements java.io.Serializable { } } - private void declareAttackersTurnBasedActions() { - Player whoDeclares = playerDeclaresAttackers == null || playerDeclaresAttackers.hasLost() ? playerTurn : playerDeclaresAttackers; - whoDeclares.getController().declareAttackers(playerTurn); - - if ( game.isGameOver() ) // they just like to close window at any moment - return; - - 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 ) { - if (!c2.hasKeyword("Vigilance")) - c2.tap(); - } else { - game.getCombat().removeFromCombat(c2); - } - } - - // 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); - for (Card card : attackingPlayer.getCardsIn(ZoneType.Battlefield)) { - int exaltedMagnitude = card.getKeywordAmount("Exalted"); - if (exaltedMagnitude > 0) { - CombatUtil.executeExaltedAbility(game, attacker, exaltedMagnitude, card); - } - } - } - - // fire trigger - final HashMap runParams = new HashMap(); - runParams.put("Attackers", game.getCombat().getAttackers()); - runParams.put("AttackingPlayer", game.getCombat().getAttackingPlayer()); - game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false); - - for (final Card c : game.getCombat().getAttackers()) { - CombatUtil.checkDeclareAttackers(game, c); + private Combat declareAttackersTurnBasedAction() { + Player whoDeclares = playerDeclaresAttackers == null || playerDeclaresAttackers.hasLost() ? playerTurn : playerDeclaresAttackers; + whoDeclares.getController().declareAttackers(playerTurn, combat); + + if ( game.isGameOver() ) // they just like to close window at any moment + return null; + + combat.removeAbsentCombatants(); + CombatUtil.checkAttackOrBlockAlone(combat); + + // TODO move propaganda to happen as the Attacker is Declared + for (final Card c2 : combat.getAttackers()) { + boolean canAttack = CombatUtil.checkPropagandaEffects(game, c2, combat); + if ( canAttack ) { + if (!c2.hasKeyword("Vigilance")) + c2.tap(); + } else { + combat.removeFromCombat(c2); } } + // Prepare and fire event 'attackers declared' + MapOfLists attackersMap = new HashMapOfLists(CollectionSuppliers.arrayLists()); + for(GameEntity ge : combat.getDefenders()) attackersMap.addAll(ge, combat.getAttackersOf(ge)); + game.fireEvent(new GameEventAttackersDeclared(playerTurn, attackersMap)); - private void declareBlockersTurnBaseActions() { - final Combat combat = game.getCombat(); + // This Exalted handler should be converted to script + if (combat.getAttackers().size() == 1) { + final Player attackingPlayer = combat.getAttackingPlayer(); + final Card attacker = combat.getAttackers().get(0); + for (Card card : attackingPlayer.getCardsIn(ZoneType.Battlefield)) { + int exaltedMagnitude = card.getKeywordAmount("Exalted"); + if (exaltedMagnitude > 0) { + CombatUtil.executeExaltedAbility(game, attacker, exaltedMagnitude, card); + } + } + } + + // fire trigger + final HashMap runParams = new HashMap(); + runParams.put("Attackers", combat.getAttackers()); + runParams.put("AttackingPlayer", combat.getAttackingPlayer()); + game.getTriggerHandler().runTrigger(TriggerType.AttackersDeclared, runParams, false); + + for (final Card c : combat.getAttackers()) { + CombatUtil.checkDeclaredAttacker(game, c, combat); + } + return combat; + } + + + private void declareBlockersTurnBasedAction() { Player p = playerTurn; do { p = game.getNextPlayerAfter(p); // Apply Odric's effect here Player whoDeclaresBlockers = playerDeclaresBlockers == null || playerDeclaresBlockers.hasLost() ? p : playerDeclaresBlockers; - if ( combat.isPlayerAttacked(p) ) - whoDeclaresBlockers.getController().declareBlockers(p); + if ( combat.isPlayerAttacked(p) ) { + whoDeclaresBlockers.getController().declareBlockers(p, combat); + game.fireEvent(new GameEventBlockerAssigned()); // + } if ( game.isGameOver() ) // they just like to close window at any moment return; } while(p != playerTurn); - + CombatUtil.orderMultipleBlockers(combat); + CombatUtil.orderBlockingMultipleAttackers(combat); + combat.removeAbsentCombatants(); @@ -545,14 +547,14 @@ public class PhaseHandler implements java.io.Serializable { } } for (Card c : filterList) { - if (c.hasKeyword("CARDNAME can't attack or block alone.") && c.isBlocking()) { + if (c.hasKeyword("CARDNAME can't attack or block alone.") && combat.isBlocking(c)) { if (combat.getAllBlockers().size() < 2) { combat.undoBlockingAssignment(c); } } } - combat.setUnblockedAttackers(); + combat.onBlockersDeclared(); List list = combat.getAllBlockers(); @@ -563,7 +565,7 @@ public class PhaseHandler implements java.io.Serializable { } }); - CombatUtil.checkDeclareBlockers(game, list); + CombatUtil.checkDeclareBlockers(game, list, combat); for (final Card a : combat.getAttackers()) { CombatUtil.checkBlockedAttackers(game, a, combat.getBlockers(a)); @@ -571,10 +573,10 @@ public class PhaseHandler implements java.io.Serializable { // map: defender => (many) attacker => (many) blocker Map> blockers = new HashMap>(); - for(GameEntity ge : game.getCombat().getDefenders()) { + for(GameEntity ge : combat.getDefenders()) { MapOfLists protectThisDefender = new HashMapOfLists(CollectionSuppliers.arrayLists()); - for(Card att : game.getCombat().getAttackersOf(ge)) { - protectThisDefender.addAll(att, game.getCombat().getBlockers(att)); + for(Card att : combat.getAttackersOf(ge)) { + protectThisDefender.addAll(att, combat.getBlockers(att)); } blockers.put(ge, protectThisDefender); } @@ -896,6 +898,7 @@ public class PhaseHandler implements java.io.Serializable { setPlayerTurn(player0); game.fireEvent(new GameEventTurnPhase(this.getPlayerTurn(), this.getPhase(), "")); + combat = null; // not-null can be created only when declare attackers phase begins } /** @@ -904,6 +907,7 @@ public class PhaseHandler implements java.io.Serializable { * @param phaseID the new phase state */ public final void endTurnByEffect() { + this.combat = null; this.phase = PhaseType.CLEANUP; this.onPhaseBegin(); } @@ -945,6 +949,11 @@ public class PhaseHandler implements java.io.Serializable { public final void setPlayerDeclaresAttackers(Player player) { this.playerDeclaresAttackers = player; } + + + public void endCombat() { + combat = null; + } diff --git a/src/main/java/forge/game/player/PlayerController.java b/src/main/java/forge/game/player/PlayerController.java index 1514b2883fa..f44e742ef2a 100644 --- a/src/main/java/forge/game/player/PlayerController.java +++ b/src/main/java/forge/game/player/PlayerController.java @@ -20,6 +20,7 @@ import forge.card.spellability.TargetChoices; import forge.deck.Deck; import forge.game.Game; import forge.game.GameType; +import forge.game.phase.Combat; import forge.game.phase.PhaseType; import forge.game.zone.ZoneType; @@ -143,8 +144,8 @@ public abstract class PlayerController { public abstract boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question); public abstract List getCardsToMulligan(boolean isCommander, Player firstPlayer); - public abstract void declareAttackers(Player attacker); - public abstract void declareBlockers(Player defender); + public abstract void declareAttackers(Player attacker, Combat combat); + public abstract void declareBlockers(Player defender, Combat combat); public abstract void takePriority(); public abstract List chooseCardsToDiscardToMaximumHandSize(int numDiscard); diff --git a/src/main/java/forge/game/player/PlayerControllerAi.java b/src/main/java/forge/game/player/PlayerControllerAi.java index 5b341e67682..9e9f860a2a6 100644 --- a/src/main/java/forge/game/player/PlayerControllerAi.java +++ b/src/main/java/forge/game/player/PlayerControllerAi.java @@ -32,9 +32,10 @@ import forge.game.Game; import forge.game.GameType; import forge.game.ai.AiController; import forge.game.ai.ComputerUtil; -import forge.game.ai.ComputerUtilBlock; +import forge.game.ai.AiBlockController; import forge.game.ai.ComputerUtilCombat; import forge.game.ai.ComputerUtilCost; +import forge.game.phase.Combat; import forge.game.zone.ZoneType; import forge.util.Aggregates; import forge.util.MyRandom; @@ -166,12 +167,12 @@ public class PlayerControllerAi extends PlayerController { @Override public List orderBlockers(Card attacker, List blockers) { - return ComputerUtilBlock.orderBlockers(attacker, blockers); + return AiBlockController.orderBlockers(attacker, blockers); } @Override public List orderAttackers(Card blocker, List attackers) { - return ComputerUtilBlock.orderAttackers(blocker, attackers); + return AiBlockController.orderAttackers(blocker, attackers); } /* (non-Javadoc) @@ -322,14 +323,14 @@ public class PlayerControllerAi extends PlayerController { } @Override - public void declareAttackers(Player attacker) { - brains.declareAttackers(attacker); + public void declareAttackers(Player attacker, Combat combat) { + brains.declareAttackers(attacker, combat); } @Override - public void declareBlockers(Player defender) { - brains.declateBlockers(defender); + public void declareBlockers(Player defender, Combat combat) { + brains.declareBlockersFor(defender, combat); } @Override diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index 3660f463ab2..cedf3906af2 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -33,6 +33,7 @@ import forge.deck.Deck; import forge.deck.DeckSection; import forge.game.Game; import forge.game.GameType; +import forge.game.phase.Combat; import forge.game.phase.PhaseType; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -45,7 +46,6 @@ import forge.gui.input.InputPassPriority; import forge.gui.input.InputPlayOrDraw; import forge.gui.input.InputSelectCards; import forge.gui.input.InputSelectCardsFromList; -import forge.gui.input.InputSynchronized; import forge.gui.match.CMatchUI; import forge.item.PaperCard; import forge.properties.ForgePreferences.FPref; @@ -525,21 +525,19 @@ public class PlayerControllerHuman extends PlayerController { } @Override - public void declareAttackers(Player attacker) { - game.getCombat().initiatePossibleDefenders(attacker.getOpponents()); + public void declareAttackers(Player attacker, Combat combat) { // This input should not modify combat object itself, but should return user choice - InputSynchronized inpAttack = new InputAttack(attacker, player, game.getCombat()); + InputAttack inpAttack = new InputAttack(attacker, player, combat); Singletons.getControl().getInputQueue().setInputAndWait(inpAttack); } @Override - public void declareBlockers(Player defender) { + public void declareBlockers(Player defender, Combat combat) { // This input should not modify combat object itself, but should return user choice - InputSynchronized inpBlock = new InputBlock(player, defender, game.getCombat()); + InputBlock inpBlock = new InputBlock(player, defender, combat); Singletons.getControl().getInputQueue().setInputAndWait(inpBlock); } - @Override public void takePriority() { PhaseType phase = game.getPhaseHandler().getPhase(); diff --git a/src/main/java/forge/gui/GuiDisplayUtil.java b/src/main/java/forge/gui/GuiDisplayUtil.java index 409f2de3339..e262920acd7 100644 --- a/src/main/java/forge/gui/GuiDisplayUtil.java +++ b/src/main/java/forge/gui/GuiDisplayUtil.java @@ -151,7 +151,7 @@ public final class GuiDisplayUtil { game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn); - game.getCombat().reset(game.getPhaseHandler().getPlayerTurn()); + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); devSetupPlayerState(humanLife, humanCardTexts, human); diff --git a/src/main/java/forge/gui/input/InputAttack.java b/src/main/java/forge/gui/input/InputAttack.java index 493d71903bf..ea357f2b94b 100644 --- a/src/main/java/forge/gui/input/InputAttack.java +++ b/src/main/java/forge/gui/input/InputAttack.java @@ -129,10 +129,10 @@ public class InputAttack extends InputSyncronizedBase { return; } - if (card.isAttacking(currentDefender)) { + if (combat.isAttacking(card, currentDefender)) { // Activate band by selecting/deselecting a band member if (this.activeBand == null) { - this.activateBand(combat.getBandByAttacker(card)); + this.activateBand(combat.getBandOfAttacker(card)); } else if (this.activeBand.getAttackers().contains(card)) { this.activateBand(null); } else { // Join a band by selecting a non-active band member after activating a band diff --git a/src/main/java/forge/gui/input/InputBlock.java b/src/main/java/forge/gui/input/InputBlock.java index 726bfafeb8c..d197a77bcdb 100644 --- a/src/main/java/forge/gui/input/InputBlock.java +++ b/src/main/java/forge/gui/input/InputBlock.java @@ -17,16 +17,15 @@ */ package forge.gui.input; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - import forge.Card; +import forge.Singletons; import forge.game.phase.Combat; import forge.game.phase.CombatUtil; import forge.game.player.Player; import forge.game.zone.ZoneType; +import forge.gui.GuiDialog; import forge.gui.match.CMatchUI; +import forge.sound.SoundEffectType; import forge.view.ButtonUtil; /** @@ -42,7 +41,7 @@ public class InputBlock extends InputSyncronizedBase { private static final long serialVersionUID = 6120743598368928128L; private Card currentAttacker = null; - private final HashMap> allBlocking = new HashMap>(); + // some cards may block several creatures at a time. (ex: Two-Headed Dragon, Vanguard's Shield) private final Combat combat; private final Player defender; private final Player declarer; @@ -57,10 +56,6 @@ public class InputBlock extends InputSyncronizedBase { this.combat = combat; } - private final void removeFromAllBlocking(final Card c) { - this.allBlocking.remove(c); - } - /** {@inheritDoc} */ @Override protected final void showMessage() { @@ -90,14 +85,14 @@ public class InputBlock extends InputSyncronizedBase { /** {@inheritDoc} */ @Override public final void onOk() { - if (CombatUtil.finishedMandatoryBlocks(combat, defender)) { + String blockErrors = CombatUtil.validateBlocks(combat, defender); + if( null == blockErrors ) { // Done blocking ButtonUtil.reset(); - CombatUtil.orderMultipleCombatants(combat); - currentAttacker = null; - allBlocking.clear(); - + setCurrentAttacker(null); stop(); + } else { + GuiDialog.message(blockErrors); } } @@ -105,43 +100,48 @@ public class InputBlock extends InputSyncronizedBase { @Override public final void onCardSelected(final Card card, boolean isMetaDown) { - if (isMetaDown) { - if (card.getController() == defender ) { - combat.removeFromCombat(card); - } - removeFromAllBlocking(card); + if (isMetaDown && card.getController() == defender) { + combat.removeFromCombat(card); CMatchUI.SINGLETON_INSTANCE.showCombat(); return; } // is attacking? - boolean reminder = true; + boolean isCorrectAction = false; - if (combat.getAttackers().contains(card)) { - this.currentAttacker = card; - reminder = false; + if (combat.isAttacking(card)) { + setCurrentAttacker(card); + isCorrectAction = true; } else { // Make sure this card is valid to even be a blocker if (this.currentAttacker != null && card.isCreature() && defender.getZone(ZoneType.Battlefield).contains(card)) { - // Create a new blockedBy list if it doesn't exist - if (!this.allBlocking.containsKey(card)) { - this.allBlocking.put(card, new ArrayList()); - } - - List attackersBlocked = this.allBlocking.get(card); - if (!attackersBlocked.contains(this.currentAttacker) - && CombatUtil.canBlock(this.currentAttacker, card, combat)) { - attackersBlocked.add(this.currentAttacker); + isCorrectAction = CombatUtil.canBlock(this.currentAttacker, card, combat); + if ( isCorrectAction ) { combat.addBlocker(this.currentAttacker, card); - reminder = false; + // This call is performed from GUI and is not intended to propagate to log or net. + // No need to use event bus then + Singletons.getControl().getSoundSystem().play(SoundEffectType.Block); } } } - if (reminder) { + if (!isCorrectAction) { flashIncorrectAction(); } this.showMessage(); } // selectCard() + + + private void setCurrentAttacker(Card card) { + currentAttacker = card; + Player attacker = null; + for(Card c : combat.getAttackers()) { + c.setUsedToPay(card == c); + if ( attacker == null ) + attacker = c.getController(); + } + // request redraw from here + attacker.getZone(ZoneType.Battlefield).updateObservers(); + } } diff --git a/src/main/java/forge/gui/match/CMatchUI.java b/src/main/java/forge/gui/match/CMatchUI.java index 1a4821bd900..029dfb36ae2 100644 --- a/src/main/java/forge/gui/match/CMatchUI.java +++ b/src/main/java/forge/gui/match/CMatchUI.java @@ -261,7 +261,7 @@ public enum CMatchUI { public void showCombat() { - if ( CCombat.SINGLETON_INSTANCE.hasCombatToShow() ) { + if (Singletons.getControl().getObservedGame().getPhaseHandler().inCombat()) { SDisplayUtil.showTab(EDocID.REPORT_COMBAT.getDoc()); } CCombat.SINGLETON_INSTANCE.update(); diff --git a/src/main/java/forge/gui/match/TargetingOverlay.java b/src/main/java/forge/gui/match/TargetingOverlay.java index 7240c0d6255..04efba63d6c 100644 --- a/src/main/java/forge/gui/match/TargetingOverlay.java +++ b/src/main/java/forge/gui/match/TargetingOverlay.java @@ -339,12 +339,12 @@ public enum TargetingOverlay { if (overlaystate == 0) { return; } // Arc drawing - assembleArcs(combat); + if( null != combat ) + assembleArcs(combat); if (arcs.isEmpty()) { return; } Graphics2D g2d = (Graphics2D) g; - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Color color = FSkin.getColor(FSkin.Colors.CLR_ACTIVE); for (Point[] p : arcs) { diff --git a/src/main/java/forge/gui/match/controllers/CCombat.java b/src/main/java/forge/gui/match/controllers/CCombat.java index 65a65e291ae..6faed7e75be 100644 --- a/src/main/java/forge/gui/match/controllers/CCombat.java +++ b/src/main/java/forge/gui/match/controllers/CCombat.java @@ -9,7 +9,6 @@ import forge.game.Game; import forge.game.combat.AttackingBand; import forge.game.phase.Combat; import forge.game.phase.PhaseHandler; -import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.gui.framework.ICDoc; import forge.gui.match.views.VCombat; @@ -42,20 +41,14 @@ public enum CCombat implements ICDoc { public void initialize() { } - public final boolean hasCombatToShow() { - PhaseHandler pH = game.getPhaseHandler(); - PhaseType ph = pH.getPhase(); - - return game.getCombat().isCombat() && ph.isAfter(PhaseType.COMBAT_BEGIN) && ph.isBefore(PhaseType.END_OF_TURN); - } - /* (non-Javadoc) * @see forge.gui.framework.ICDoc#update() */ @Override public void update() { - if (hasCombatToShow()) // display combat - VCombat.SINGLETON_INSTANCE.updateCombat(game.getCombat().getAttackers().size(), getCombatDescription(game.getCombat())); + PhaseHandler pH = game.getPhaseHandler(); + if (pH.inCombat()) // display combat + VCombat.SINGLETON_INSTANCE.updateCombat(pH.getCombat().getAttackers().size(), getCombatDescription(pH.getCombat())); else VCombat.SINGLETON_INSTANCE.updateCombat(0, ""); } @@ -99,7 +92,7 @@ public enum CCombat implements ICDoc { if (isBand) { // Only print Band data if it's actually a band display.append(" > BAND"); - if (band.getBlocked()) { + if (band.isBlocked()) { display.append(" (blocked)"); } display.append("\n"); @@ -110,14 +103,14 @@ public enum CCombat implements ICDoc { display.append(combatantToString(c)).append("\n"); } - if (!isBand && band.getBlockers().isEmpty()) { + List blockers = combat.getBlockers(band); + if (!isBand && blockers.isEmpty()) { // if single creature is blocked, but no longer has blockers, tell the user! - if (band.getBlocked()) { - display.append(" (blocked) "); - } + if (band.isBlocked()) + display.append(" (blocked)\n"); } - for (final Card element : band.getBlockers()) { + for (final Card element : blockers) { display.append(" < ").append(combatantToString(element)).append("\n"); } previousBand = isBand; diff --git a/src/main/java/forge/gui/match/controllers/CDock.java b/src/main/java/forge/gui/match/controllers/CDock.java index df53de8a2d5..2b542858722 100644 --- a/src/main/java/forge/gui/match/controllers/CDock.java +++ b/src/main/java/forge/gui/match/controllers/CDock.java @@ -35,6 +35,7 @@ import forge.CardPredicates.Presets; import forge.Command; import forge.deck.Deck; import forge.game.Game; +import forge.game.phase.Combat; import forge.game.phase.CombatUtil; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; @@ -184,17 +185,19 @@ public enum CDock implements ICDoc { public void alphaStrike() { final PhaseHandler ph = game.getPhaseHandler(); - Player p = findAffectedPlayer(); - if (ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS, p)) { // ph.is(...) includes null check + final Player p = findAffectedPlayer(); + final Game game = p.getGame(); + Combat combat = game.getCombat(); + if (ph.is(PhaseType.COMBAT_DECLARE_ATTACKERS, p) && combat!= null) { // ph.is(...) includes null check List defenders = p.getOpponents(); for (Card c : CardLists.filter(p.getCardsIn(ZoneType.Battlefield), Presets.CREATURES)) { - if (c.isAttacking()) + if (combat.isAttacking(c)) continue; for(Player defender : defenders) - if( CombatUtil.canAttack(c, defender, game.getCombat())) { - game.getCombat().addAttacker(c, defender); + if( CombatUtil.canAttack(c, defender, combat)) { + combat.addAttacker(c, defender); break; } } diff --git a/src/main/java/forge/sound/EventVisualizer.java b/src/main/java/forge/sound/EventVisualizer.java index 219f827b72a..2f42a783bb7 100644 --- a/src/main/java/forge/sound/EventVisualizer.java +++ b/src/main/java/forge/sound/EventVisualizer.java @@ -3,7 +3,6 @@ package forge.sound; import forge.Card; import forge.Singletons; import forge.card.spellability.SpellAbility; -import forge.game.event.GameEventBlockerAssigned; import forge.game.event.GameEventCardChangeZone; import forge.game.event.GameEventCardDamaged; import forge.game.event.GameEventCardDestroyed; @@ -32,8 +31,6 @@ import forge.game.zone.ZoneType; */ public class EventVisualizer extends IGameEventVisitor.Base { - - public SoundEffectType visit(GameEventBlockerAssigned event) { return SoundEffectType.Block; } public SoundEffectType visit(GameEventCardDamaged event) { return SoundEffectType.Damage; } public SoundEffectType visit(GameEventCardDestroyed event) { return SoundEffectType.Destroy; } public SoundEffectType visit(GameEventCardEquipped event) { return SoundEffectType.Equip; } diff --git a/src/main/java/forge/util/maps/EnumMapOfLists.java b/src/main/java/forge/util/maps/EnumMapOfLists.java index f7091b233e5..9e9482c7b56 100644 --- a/src/main/java/forge/util/maps/EnumMapOfLists.java +++ b/src/main/java/forge/util/maps/EnumMapOfLists.java @@ -28,7 +28,7 @@ public class EnumMapOfLists, V> extends EnumMap ensureCollectionFor(K key) { + public Collection ensureCollectionFor(K key) { Collection value = get(key); if ( value == null ) { value = factory.get(); diff --git a/src/main/java/forge/util/maps/HashMapOfLists.java b/src/main/java/forge/util/maps/HashMapOfLists.java index 3aa411a02d1..e76dee73b13 100644 --- a/src/main/java/forge/util/maps/HashMapOfLists.java +++ b/src/main/java/forge/util/maps/HashMapOfLists.java @@ -32,7 +32,7 @@ public class HashMapOfLists extends HashMap> implements M private static final long serialVersionUID = 3029089910183132930L; - private Collection ensureCollectionFor(K key) { + public Collection ensureCollectionFor(K key) { Collection value = get(key); if ( value == null ) { value = factory.get(); diff --git a/src/main/java/forge/util/maps/MapOfLists.java b/src/main/java/forge/util/maps/MapOfLists.java index 51f0288485e..89d21691061 100644 --- a/src/main/java/forge/util/maps/MapOfLists.java +++ b/src/main/java/forge/util/maps/MapOfLists.java @@ -6,4 +6,5 @@ import java.util.Map; public interface MapOfLists extends Map> { void add(K key, V element); void addAll(K key, Collection element); + Collection ensureCollectionFor(K key); } \ No newline at end of file diff --git a/src/main/java/forge/util/maps/TreeMapOfLists.java b/src/main/java/forge/util/maps/TreeMapOfLists.java index 0155d36d908..f6e40dced67 100644 --- a/src/main/java/forge/util/maps/TreeMapOfLists.java +++ b/src/main/java/forge/util/maps/TreeMapOfLists.java @@ -33,7 +33,7 @@ public class TreeMapOfLists extends TreeMap> implements M this.factory = factory; } - private Collection ensureCollectionFor(K key) { + public Collection ensureCollectionFor(K key) { Collection value = get(key); if ( value == null ) { value = factory.get(); diff --git a/src/main/java/forge/view/arcane/CardPanel.java b/src/main/java/forge/view/arcane/CardPanel.java index 279b12fcfce..2c255af18b4 100644 --- a/src/main/java/forge/view/arcane/CardPanel.java +++ b/src/main/java/forge/view/arcane/CardPanel.java @@ -42,6 +42,7 @@ import forge.ImageCache; import forge.Singletons; import forge.card.CardEdition; import forge.card.mana.ManaCost; +import forge.game.phase.Combat; import forge.gui.CardContainer; import forge.gui.toolbox.CardFaceSymbols; import forge.properties.ForgePreferences.FPref; @@ -407,10 +408,12 @@ public class CardPanel extends JPanel implements CardContainer { final int stateXSymbols = (this.cardXOffset + (this.cardWidth / 2)) - 16; final int ySymbols = (this.cardYOffset + this.cardHeight) - (this.cardHeight / 8) - 16; // int yOff = (cardHeight/4) + 2; - if (card.isAttacking()) { - CardFaceSymbols.drawSymbol("attack", g, combatXSymbols, ySymbols); - } else if (card.isBlocking()) { - CardFaceSymbols.drawSymbol("defend", g, combatXSymbols, ySymbols); + Combat combat = card.getGame().getCombat(); + if( combat != null ) { + if ( combat.isAttacking(card)) + CardFaceSymbols.drawSymbol("attack", g, combatXSymbols, ySymbols); + if ( combat.isBlocking(card)) + CardFaceSymbols.drawSymbol("defend", g, combatXSymbols, ySymbols); } if (card.isSick() && card.isInPlay()) {