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
This commit is contained in:
Maxmtg
2013-06-24 09:33:19 +00:00
parent 394a322fbe
commit 74059bda82
54 changed files with 963 additions and 1541 deletions

View File

@@ -5357,6 +5357,8 @@ public class Card extends GameEntity implements Comparable<Card> {
*/
@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<Card> {
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<Card> {
}
} 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<Card> {
}
}
} 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<Card> {
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<Card> c = source.getGame().getStack().getCardsCastThisTurn();
final List<Card> 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<Card> {
}
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<Card> {
return false;
} else if (restriction.equals("EachTopLibrary")) {
final List<Card> list = new ArrayList<Card>();
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<Card> {
}
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<Card> {
}
return false;
} else if (restriction.equals("NonToken")) {
final List<Card> list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield),
final List<Card> 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<Card> {
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<Card> {
return false;
}
} else if (property.startsWith("greatestPower")) {
final List<Card> list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
final List<Card> 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<Card> {
}
}
} else if (property.startsWith("leastPower")) {
final List<Card> list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
final List<Card> 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<Card> list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
final List<Card> 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<Card> list = CardLists.filter(getGame().getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
final List<Card> 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<Card> {
List<Card> list = new ArrayList<Card>();
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<Card> {
return false;
}
} else if (property.startsWith("lowestCMC")) {
final List<Card> list = getGame().getCardsIn(ZoneType.Battlefield);
final List<Card> 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<Card> {
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<Card> {
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<Card> 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<Card> {
}
}
}
} 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<Card> {
// 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<Card> {
this.usedToPayCost = b;
}
/**
* <p>
* isAttacking.
* </p>
*
* @return a boolean.
*/
public final boolean isAttacking() {
return getGame().getCombat().isAttacking(this);
}
/**
* <p>
* isAttacking.
* </p>
* @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);
}
/**
* <p>
* isBlocking.
* </p>
*
* @return a boolean.
*/
public final boolean isBlocking() {
final List<Card> blockers = getGame().getCombat().getAllBlockers();
return blockers.contains(this);
}
/**
* <p>
* isBlocked.
* </p>
*
* @return a boolean.
*/
public final boolean isBlocked() {
return getGame().getCombat().isBlocked(this);
}
/**
* <p>
* isBlocking.
* </p>
*
* @param attacker
* a {@link forge.Card} object.
* @return a boolean.
*/
public final boolean isBlocking(final Card attacker) {
return getGame().getCombat().getAttackersBlockedBy(this).contains(attacker);
}
/**
* <p>
* isBlockedBy.
* </p>
*
* @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

View File

@@ -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)) {

View File

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

View File

@@ -817,7 +817,12 @@ public class AttachAi extends SpellAbilityAi {
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@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<String> 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;
}

View File

@@ -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<Card> list) {
// Creating a new combat for testing purposes.
Combat combat = new Combat();
combat.initiatePossibleDefenders(ai);
List<Card> 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<Card> list = CardLists.getValidCards(ai.getGame().getCardsIn(origin), tgt.getValidTgts(), ai, source);
List<Card> 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<ITargetable> objects = ComputerUtil.predictThreatenedObjects(ai, sa);
final List<Card> threatenedTargets = new ArrayList<Card>();
@@ -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<Card> 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;
}

View File

@@ -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<Card>() {
@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<Card> better = CardLists.filter(options, new Predicate<Card>() {
@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()) {

View File

@@ -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<Card>() {
@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()) {

View File

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

View File

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

View File

@@ -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<Card> cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa);
if (!cards.isEmpty()) {
cards = CardLists.filter(cards, new Predicate<Card>() {
@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<String>(), false);
}
return true;
final Combat combat = game.getCombat();
return Iterables.any(cards, new Predicate<Card>() {
@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<String>(), 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<Card> list = getCurseCreatures(ai, sa, kws);
List<Card> list = getCurseCreatures(ai, sa, kws == null ? Lists.<String>newArrayList() : kws);
list = CardLists.getValidCards(list, tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard());
// several uses here:

View File

@@ -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<Card> options, boolean isOptional) {
public Card chooseSingleCard(final Player ai, SpellAbility sa, List<Card> options, boolean isOptional) {
Card choice = null;
final String logic = sa.getParam("AILogic");
if (logic == null) {
final List<Card> attackers = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttackNextTurn(c);
}
});
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
@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<Card> attackers = CardLists.filter(options, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return CombatUtil.canAttackNextTurn(c);
}
});
final List<Card> unblockables = CardLists.filter(attackers, new Predicate<Card>() {
@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;
}
}

View File

@@ -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)) {

View File

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

View File

@@ -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<Card> 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<Card> 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<String> 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<Card> blockedBy = game.getCombat().getAttackersBlockedBy(c);
List<Card> 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<Card> 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<Card> 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<Card> getCurseCreatures(final Player ai, final SpellAbility sa, final int defense, final int attack, final List<String> keywords) {
List<Card> 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<Card>() {
@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) {

View File

@@ -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)) {

View File

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

View File

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

View File

@@ -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<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Attacker", c);

View File

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

View File

@@ -100,7 +100,7 @@ public class ChooseColorEffect extends SpellAbilityEffect {
final List<Card> 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")) {

View File

@@ -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<Card>() {
@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));

View File

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

View File

@@ -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.

View File

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

View File

@@ -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<GameEntity> defs = c.getController().getGame().getCombat().getDefenders();
if (this.tokenAttacking && game.getPhaseHandler().inCombat()) {
Combat combat = game.getPhaseHandler().getCombat();
final List<GameEntity> 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);

View File

@@ -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.

View File

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

View File

@@ -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<Card> 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
/**
* <p>
@@ -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<GameEntity> 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<GameEntity> 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<Card> attackersLeft = new ArrayList<Card>(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()
/**

View File

@@ -41,162 +41,36 @@ import forge.game.player.Player;
* @author Forge
* @version $Id$
*/
public class ComputerUtilBlock {
public class AiBlockController {
private final Player ai;
/** Constant <code>attackers</code>. */
private static List<Card> attackers = new ArrayList<Card>(); // all attackers
private List<Card> attackers = new ArrayList<Card>(); // all attackers
/** Constant <code>attackersLeft</code>. */
private static List<Card> attackersLeft = new ArrayList<Card>(); // keeps track of
private List<Card> attackersLeft = new ArrayList<Card>(); // keeps track of
// all currently
// unblocked
// attackers
/** Constant <code>blockedButUnkilled</code>. */
private static List<Card> blockedButUnkilled = new ArrayList<Card>(); // blocked
private List<Card> blockedButUnkilled = new ArrayList<Card>(); // blocked
// attackers
// that
// currently
// wouldn't be
// destroyed
/** Constant <code>blockersLeft</code>. */
private static List<Card> blockersLeft = new ArrayList<Card>(); // keeps track of all
private List<Card> blockersLeft = new ArrayList<Card>(); // keeps track of all
// unassigned
// blockers
/** Constant <code>diff=0</code>. */
private static int diff = 0;
private int diff = 0;
private static boolean lifeInDanger = false;
/**
* <p>
* Getter for the field <code>attackers</code>.
* </p>
*
* @return a {@link forge.CardList} object.
*/
private static List<Card> getAttackers() {
return ComputerUtilBlock.attackers;
private boolean lifeInDanger = false;
public AiBlockController(Player aiPlayer) {
this.ai = aiPlayer;
}
/**
* <p>
* Setter for the field <code>attackers</code>.
* </p>
*
* @param cardList
* a {@link forge.CardList} object.
*/
private static void setAttackers(final List<Card> cardList) {
ComputerUtilBlock.attackers = (cardList);
}
/**
* <p>
* Getter for the field <code>attackersLeft</code>.
* </p>
*
* @return a {@link forge.CardList} object.
*/
private static List<Card> getAttackersLeft() {
return ComputerUtilBlock.attackersLeft;
}
/**
* <p>
* Setter for the field <code>attackersLeft</code>.
* </p>
*
* @param cardList
* a {@link forge.CardList} object.
*/
private static void setAttackersLeft(final List<Card> cardList) {
ComputerUtilBlock.attackersLeft = (cardList);
}
/**
* <p>
* Getter for the field <code>blockedButUnkilled</code>.
* </p>
*
* @return a {@link forge.CardList} object.
*/
private static List<Card> getBlockedButUnkilled() {
return ComputerUtilBlock.blockedButUnkilled;
}
/**
* <p>
* Setter for the field <code>blockedButUnkilled</code>.
* </p>
*
* @param cardList
* a {@link forge.CardList} object.
*/
private static void setBlockedButUnkilled(final List<Card> cardList) {
ComputerUtilBlock.blockedButUnkilled = (cardList);
}
/**
* <p>
* Getter for the field <code>blockersLeft</code>.
* </p>
*
* @return a {@link forge.CardList} object.
*/
private static List<Card> getBlockersLeft() {
return ComputerUtilBlock.blockersLeft;
}
/**
* <p>
* Setter for the field <code>blockersLeft</code>.
* </p>
*
* @param cardList
* a {@link forge.CardList} object.
*/
private static void setBlockersLeft(final List<Card> cardList) {
ComputerUtilBlock.blockersLeft = (cardList);
}
/**
* <p>
* Getter for the field <code>diff</code>.
* </p>
*
* @return a int.
*/
private static int getDiff() {
return ComputerUtilBlock.diff;
}
/**
* <p>
* Setter for the field <code>diff</code>.
* </p>
*
* @param diff
* a int.
*/
private static void setDiff(final int diff) {
ComputerUtilBlock.diff = (diff);
}
// finds the creatures able to block the attacker
/**
* <p>
* getPossibleBlockers.
* </p>
*
* @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<Card> getPossibleBlockers(final Card attacker, final List<Card> blockersLeft, final Combat combat
, final boolean solo) {
private List<Card> getPossibleBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft, final boolean solo) {
final List<Card> blockers = new ArrayList<Card>();
for (final Card blocker : blockersLeft) {
@@ -214,20 +88,7 @@ public class ComputerUtilBlock {
}
// finds blockers that won't be destroyed
/**
* <p>
* getSafeBlockers.
* </p>
*
* @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<Card> getSafeBlockers(final Player ai, final Card attacker, final List<Card> blockersLeft, final Combat combat) {
private List<Card> getSafeBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
final List<Card> blockers = new ArrayList<Card>();
for (final Card b : blockersLeft) {
@@ -240,20 +101,7 @@ public class ComputerUtilBlock {
}
// finds blockers that destroy the attacker
/**
* <p>
* getKillingBlockers.
* </p>
*
* @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<Card> getKillingBlockers(final Player ai, final Card attacker, final List<Card> blockersLeft, final Combat combat) {
private List<Card> getKillingBlockers(final Combat combat, final Card attacker, final List<Card> blockersLeft) {
final List<Card> blockers = new ArrayList<Card>();
for (final Card b : blockersLeft) {
@@ -267,7 +115,7 @@ public class ComputerUtilBlock {
public final static List<List<Card>> sortAttackerByDefender(Combat combat) {
private List<List<Card>> sortAttackerByDefender(final Combat combat) {
List<GameEntity> defenders = combat.getDefenders();
final ArrayList<List<Card>> attackers = new ArrayList<List<Card>>(defenders.size());
for (GameEntity defender : defenders) {
@@ -276,7 +124,7 @@ public class ComputerUtilBlock {
return attackers;
}
public static List<Card> sortPotentialAttackers(final Player ai, final Combat combat) {
private List<Card> sortPotentialAttackers(final Combat combat) {
final List<List<Card>> attackerLists = sortAttackerByDefender(combat);
final List<Card> sortedAttackers = new ArrayList<Card>();
final List<Card> firstAttacker = attackerLists.get(0);
@@ -328,20 +176,11 @@ public class ComputerUtilBlock {
// ================================
// Good Blocks means a good trade or no trade
/**
* <p>
* makeGoodBlocks.
* </p>
*
* @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<Card> currentAttackers = new ArrayList<Card>(ComputerUtilBlock.getAttackersLeft());
List<Card> currentAttackers = new ArrayList<Card>(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<Card> blockers = ComputerUtilBlock.getPossibleBlockers(attacker,
ComputerUtilBlock.getBlockersLeft(), combat, true);
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
final List<Card> safeBlockers = ComputerUtilBlock.getSafeBlockers(ai, attacker, blockers, combat);
final List<Card> safeBlockers = getSafeBlockers(combat, attacker, blockers);
List<Card> 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<Card>(currentAttackers));
return combat;
attackersLeft = (new ArrayList<Card>(currentAttackers));
}
// Good Gang Blocks means a good trade or no trade
@@ -407,14 +244,14 @@ public class ComputerUtilBlock {
*/
static final Predicate<Card> rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT"));
private static Combat makeGangBlocks(final Player ai, final Combat combat) {
List<Card> currentAttackers = CardLists.filter(ComputerUtilBlock.getAttackersLeft(), Predicates.not(rampagesOrNeedsManyToBlock));
private void makeGangBlocks(final Combat combat) {
List<Card> currentAttackers = CardLists.filter(attackersLeft, Predicates.not(rampagesOrNeedsManyToBlock));
List<Card> 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<Card> firstStrikeBlockers = new ArrayList<Card>();
final List<Card> blockGang = new ArrayList<Card>();
for (int i = 0; i < blockers.size(); i++) {
@@ -448,12 +285,12 @@ public class ComputerUtilBlock {
}
}
ComputerUtilBlock.setAttackersLeft(new ArrayList<Card>(currentAttackers));
currentAttackers = new ArrayList<Card>(ComputerUtilBlock.getAttackersLeft());
attackersLeft = (new ArrayList<Card>(currentAttackers));
currentAttackers = new ArrayList<Card>(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<Card> usableBlockers;
final List<Card> blockGang = new ArrayList<Card>();
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<Card>(currentAttackers));
return combat;
attackersLeft = (new ArrayList<Card>(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<Card> currentAttackers = new ArrayList<Card>(ComputerUtilBlock.getAttackersLeft());
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
List<Card> 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<Card> 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<Card>(currentAttackers));
return combat;
attackersLeft = (new ArrayList<Card>(currentAttackers));
}
// Chump Blocks (should only be made if life is in danger)
/**
* <p>
* makeChumpBlocks.
* </p>
*
* @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<Card> currentAttackers = new ArrayList<Card>(ComputerUtilBlock.getAttackersLeft());
List<Card> currentAttackers = new ArrayList<Card>(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<Card> attackers) {
private void makeChumpBlocks(final Combat combat, List<Card> 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<Card> chumpBlockers = ComputerUtilBlock
.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, true);
List<Card> 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);
}
/**
* <p>
* makeMultiChumpBlocks.
* </p>
*
* 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<Card> currentAttackers = new ArrayList<Card>(ComputerUtilBlock.getAttackersLeft());
List<Card> currentAttackers = new ArrayList<Card>(attackersLeft);
for (final Card attacker : currentAttackers) {
if (!attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
continue;
}
List<Card> possibleBlockers = ComputerUtilBlock.getPossibleBlockers(attacker, ComputerUtilBlock.getBlockersLeft(), combat, true);
List<Card> 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)
/**
* <p>
* reinforceBlockersAgainstTrample.
* </p>
*
* @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<Card> chumpBlockers;
List<Card> tramplingAttackers = CardLists.getKeyword(ComputerUtilBlock.getAttackers(), "Trample");
List<Card> 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
/**
* <p>
* reinforceBlockersToKill.
* </p>
*
* @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<Card> safeBlockers;
List<Card> blockers;
List<Card> targetAttackers = CardLists.filter(ComputerUtilBlock.getBlockedButUnkilled(), Predicates.not(rampagesOrNeedsManyToBlock));
List<Card> 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;
}
/**
* <p>
* resetBlockers.
* </p>
*
* @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<Card> possibleBlockers) {
private void clearBlockers(final Combat combat, final List<Card> possibleBlockers) {
final List<Card> 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<Card>(ComputerUtilBlock.getAttackers())); // keeps
// track
// of all
// currently
// unblocked
// attackers
ComputerUtilBlock.setBlockersLeft(new ArrayList<Card>(possibleBlockers)); // keeps
// track of
// all
// unassigned
// blockers
ComputerUtilBlock.setBlockedButUnkilled(new ArrayList<Card>()); // keeps track
// of all
// blocked
// attackers that currently
// wouldn't be destroyed
return combat;
attackersLeft = new ArrayList<Card>(attackers); // keeps track of all currently unblocked attackers
blockersLeft = new ArrayList<Card>(possibleBlockers); // keeps track of all unassigned blockers
blockedButUnkilled = new ArrayList<Card>(); // keeps track of all blocked attackers that currently wouldn't be destroyed
}
// Main function
/**
* <p>
* getBlockers.
* </p>
*
* @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<Card> possibleBlockers) {
/** Assigns blockers for the provided combat instance (in favor of player passes to ctor) */
public void assignBlockers(final Combat combat) {
final List<Card> 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<Card>(ComputerUtilBlock.getAttackers()));
// keeps track of all unassigned blockers
ComputerUtilBlock.setBlockersLeft(new ArrayList<Card>(possibleBlockers));
// keeps track of all blocked attackers that currently wouldn't be destroyed
ComputerUtilBlock.setBlockedButUnkilled(new ArrayList<Card>());
clearBlockers(combat, possibleBlockers);
List<Card> blockers;
List<Card> 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<Card> orderBlockers(Card attacker, List<Card> blockers) {

View File

@@ -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<Card> 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() {

View File

@@ -676,21 +676,6 @@ public class ComputerUtil {
return returnList;
}
/**
* <p>
* getBlockers.
* </p>
*
* @return a {@link forge.game.phase.Combat} object.
*/
public static Combat getBlockers(final Player ai) {
final List<Card> blockers = ai.getCardsIn(ZoneType.Battlefield);
return ComputerUtilBlock.getBlockers(ai, ai.getGame().getCombat(), blockers);
}
/**
* <p>
* 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<Card> 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;

View File

@@ -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<Card> 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);
}
/**

View File

@@ -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<Card> 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<Card> attackers = game.getCombat().getAttackersBlockedBy(blocker);
final List<Card> 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;
}

View File

@@ -11,40 +11,27 @@ import forge.GameEntity;
* TODO: Write javadoc for this type.
*
*/
public class AttackingBand implements Comparable<AttackingBand> {
public class AttackingBand {
private List<Card> attackers = new ArrayList<Card>();
private List<Card> blockers = new ArrayList<Card>();
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<Card> 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<Card> getAttackers() { return this.attackers; }
public List<Card> 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<Card> 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<Card> band, boolean shareDamage) {
if (band.isEmpty()) {
@@ -92,26 +79,29 @@ public class AttackingBand implements Comparable<AttackingBand> {
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<Card> 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 ? ">||" : ">>>" );
}
}

View File

@@ -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;
/**
* <p>
@@ -45,166 +46,69 @@ import forge.game.zone.ZoneType;
* @version $Id$
*/
public class Combat {
// List of AttackingBands
private final List<AttackingBand> attackingBands = new ArrayList<AttackingBand>();
// Attacker -> AttackingBand (Attackers can only be in a single band)
private final Map<Card, AttackingBand> attackerToBandMap = new TreeMap<Card, AttackingBand>();
// Blocker -> AttackingBands (Blockers can block multiple bands/creatures
private final Map<Card, List<AttackingBand>> blockerToBandsMap = new TreeMap<Card, List<AttackingBand>>();
private final Player attackerPlayer;
// Defenders, as they are attacked by hostile forces
private final MapOfLists<GameEntity, AttackingBand> attackedEntities = new HashMapOfLists<GameEntity, AttackingBand>(CollectionSuppliers.<AttackingBand>arrayLists());
// Blockers to stop the hostile invaders
private final MapOfLists<AttackingBand, Card> blockedBands = new HashMapOfLists<AttackingBand, Card>(CollectionSuppliers.<Card>arrayLists());
private final HashMap<Card, Integer> defendingDamageMap = new HashMap<Card, Integer>();
// Defenders are all Opposing Players + Planeswalker's Controller By Opposing Players
private Map<GameEntity, List<Card>> defenderMap = new HashMap<GameEntity, List<Card>>();
private Map<Card, List<Card>> blockerDamageAssignmentOrder = new TreeMap<Card, List<Card>>();
private Map<Card, List<Card>> attackerDamageAssignmentOrder = new TreeMap<Card, List<Card>>();
private Map<Card, List<Card>> orderBlockerDamageAssignment = new HashMap<Card, List<Card>>();
private Map<Card, List<Card>> orderAttackerDamageAssignment = new HashMap<Card, List<Card>>();
private Player attackingPlayer = null;
/**
* <p>
* reset.
* </p>
*/
public final void reset(Player playerTurn) {
this.resetAttackers();
this.defendingDamageMap.clear();
this.attackingPlayer = playerTurn;
public Combat(Player attacker) {
attackerPlayer = attacker;
this.initiatePossibleDefenders(playerTurn.getOpponents());
}
/**
* <p>
* initiatePossibleDefenders.
* </p>
*
* @param defender
* a {@link forge.game.player.Player} object.
*/
public final void initiatePossibleDefenders(final Iterable<Player> 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<Card> 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<Card>());
List<Card> planeswalkers =
CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS);
for (final Card pw : planeswalkers) {
this.defenderMap.put(pw, new ArrayList<Card>());
for(Collection<AttackingBand> abs : attackedEntities.values()) {
if(!abs.isEmpty())
return true;
}
return false;
}
/**
* <p>
* Getter for the field <code>defenders</code>.
* </p>
*
* @return a {@link java.util.ArrayList} object.
*/
public final List<GameEntity> getDefenders() {
return new ArrayList<GameEntity>(this.defenderMap.keySet());
return Lists.newArrayList(attackedEntities.keySet());
}
/**
* <p>
* Setter for the field <code>defenders</code>.
* </p>
*
* @param newDef
* a {@link java.util.ArrayList} object.
*/
public final void setDefenders(final List<GameEntity> newDef) {
this.defenderMap.clear();
for (GameEntity entity : newDef) {
this.defenderMap.put(entity, new ArrayList<Card>());
}
}
/**
* <p>
* getDefendingPlaneswalkers.
* </p>
*
* @return an array of {@link forge.Card} objects.
*/
public final List<Player> getDefendingPlayers() {
final List<Player> defending = new ArrayList<Player>();
for (final GameEntity o : this.defenderMap.keySet()) {
for (final GameEntity o : attackedEntities.keySet()) {
if (o instanceof Player) {
defending.add((Player) o);
}
}
return defending;
}
/**
* <p>
* getDefendingPlaneswalkers.
* </p>
*
* @return an array of {@link forge.Card} objects.
*/
public final List<Card> getDefendingPlaneswalkers() {
final List<Card> pwDefending = new ArrayList<Card>();
for (final GameEntity o : this.defenderMap.keySet()) {
for (final GameEntity o : attackedEntities.keySet()) {
if (o instanceof Card) {
pwDefending.add((Card) o);
}
}
return pwDefending;
}
/**
* <p>
* Setter for the field <code>attackingPlayer</code>.
* </p>
*
* @param player
* a {@link forge.game.player.Player} object.
*/
public final void setAttackingPlayer(final Player player) {
this.attackingPlayer = player;
}
/**
* <p>
* Getter for the field <code>attackingPlayer</code>.
* </p>
*
* @return a {@link forge.game.player.Player} object.
*/
public final Player getAttackingPlayer() {
return this.attackingPlayer;
}
/**
* <p>
* addDefendingDamage.
* </p>
*
* @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<Card> getAttackersOf(GameEntity defender) {
return defenderMap.get(defender);
public final List<AttackingBand> getAttackingBandsOf(GameEntity defender) {
return Lists.newArrayList(attackedEntities.get(defender));
}
public final List<AttackingBand> getAttackingBandsOf(GameEntity defender) {
List<AttackingBand> bands = new ArrayList<AttackingBand>();
for(AttackingBand band : this.attackingBands) {
if (band.getDefender().equals(defender)) {
bands.add(band);
}
public final List<Card> getAttackersOf(GameEntity defender) {
List<Card> result = new ArrayList<Card>();
for(AttackingBand v : attackedEntities.get(defender)) {
result.addAll(v.getAttackers());
}
return bands;
return result;
}
/**
* <p>
* isAttacking.
* </p>
*
* @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<AttackingBand> 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);
}
/**
* <p>
* getDefenderByAttacker.
* </p>
*
* @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<GameEntity, Collection<AttackingBand>> 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<GameEntity, Collection<AttackingBand>> 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<AttackingBand> 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);
}
/**
* <p>
* resetAttackers.
* </p>
*/
public final void resetAttackers() {
this.attackingBands.clear();
this.blockerToBandsMap.clear();
this.attackerToBandMap.clear();
this.blockerDamageAssignmentOrder.clear();
this.attackerDamageAssignmentOrder.clear();
}
/**
* <p>
* getAttackers.
* </p>
*
* @return an array of {@link forge.Card} objects.
*/
public final List<AttackingBand> getAttackingBands() {
return attackingBands;
} // getAttackers()
List<AttackingBand> result = Lists.newArrayList();
for(Collection<AttackingBand> 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<GameEntity, Collection<AttackingBand>> ee : attackedEntities.entrySet())
for(AttackingBand ab : ee.getValue())
if (ab.contains(card))
return ee.getKey() == defender;
return false;
}
public final List<Card> getAttackers() {
List<Card> attackers = new ArrayList<Card>();
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);
}
/**
* <p>
* addBlocker.
* </p>
*
* @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);
}
}
/**
* <p>
* undoBlockingAssignment.
* </p>
*
* @param blocker
* a {@link forge.Card} object.
*/
public final void undoBlockingAssignment(final Card blocker) {
final List<AttackingBand> att = this.blockerToBandsMap.get(blocker);
for (final AttackingBand band : att) {
band.removeBlocker(blocker);
}
this.blockerToBandsMap.remove(blocker);
}
/**
* <p>
* getAllBlockers.
* </p>
*
* @return a {@link forge.CardList} object.
*/
public final List<Card> getAllBlockers() {
final List<Card> block = new ArrayList<Card>();
block.addAll(blockerToBandsMap.keySet());
return block;
}
public final List<Card> getBlockers(final AttackingBand band) {
List<Card> list = band.getBlockers();
if (list == null) {
return new ArrayList<Card>();
} else {
return new ArrayList<Card>(list);
}
List<Card> result = Lists.newArrayList();
for(Collection<AttackingBand> abs : attackedEntities.values())
for(AttackingBand ab : abs)
result.addAll(ab.getAttackers());
return result;
}
public final List<Card> getBlockers(final Card card) {
return getBlockers(card, false);
}
public final List<Card> getBlockers(final Card card, boolean ordered) {
// If requesting the ordered blocking list pass true, directly.
List<Card> 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<Card> blockers = blockedBands.get(band);
return blockers == null ? Lists.<Card>newArrayList() : Lists.newArrayList(blockers);
}
if (list == null) {
return new ArrayList<Card>();
} else {
return new ArrayList<Card>(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<Card> cc = blockedBands.get(band);
if( cc != null)
cc.remove(blocker);
}
// remove blocker from everywhere
public final void undoBlockingAssignment(final Card blocker) {
for(Collection<Card> blockers : blockedBands.values()) {
blockers.remove(blocker);
}
}
/**
* <p>
* getAttackerBlockedBy.
* </p>
*
* @param blocker
* a {@link forge.Card} object.
* @return a {@link forge.Card} object.
*/
public final List<Card> getAllBlockers() {
List<Card> result = new ArrayList<Card>();
for(Collection<Card> blockers : blockedBands.values()) {
if(!result.contains(blockers))
result.addAll(blockers);
}
return result;
}
public final List<Card> getBlockers(final AttackingBand band) {
Collection<Card> blockers = blockedBands.get(band);
return blockers == null ? Lists.<Card>newArrayList() : Lists.newArrayList(blockers);
}
public final List<Card> getAttackersBlockedBy(final Card blocker) {
List<Card> blocked = new ArrayList<Card>();
if (blockerToBandsMap.containsKey(blocker)) {
for(AttackingBand band : blockerToBandsMap.get(blocker)) {
blocked.addAll(band.getAttackers());
}
for(Entry<AttackingBand, Collection<Card>> s : blockedBands.entrySet()) {
if (s.getValue().contains(blocker))
blocked.addAll(s.getKey().getAttackers());
}
return blocked;
}
/**
* <p>
* getDefendingPlayer.
* </p>
*
* @param attacker
* a {@link forge.Card} object.
* @return a {@link forge.Player} object.
*/
public List<Player> getDefendingPlayerRelatedTo(final Card source) {
List<Player> players = new ArrayList<Player>();
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<GameEntity> 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<Card> blockers) {
this.attackerDamageAssignmentOrder.put(attacker, blockers);
this.orderAttackerDamageAssignment.put(attacker, blockers);
}
public void setBlockerDamageAssignmentOrder(final Card blocker, final List<Card> 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<Card> 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<Card> 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<AttackingBand> 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<AttackingBand, Collection<Card>> be : blockedBands.entrySet()) {
Collection<Card> blockers = be.getValue();
if(blockers.contains(c)) {
unregisterDefender(c, be.getKey());
blockers.remove(c);
}
}
} // removeFromCombat()
/**
* <p>
* verifyCreaturesInPlay.
* </p>
*/
public final void removeAbsentCombatants() {
final List<Card> all = new ArrayList<Card>();
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<GameEntity, Collection<AttackingBand>> ee : attackedEntities.entrySet()) {
for(AttackingBand ab : ee.getValue()) {
List<Card> 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<Card> toRemove = Lists.newArrayList();
for(Entry<AttackingBand, Collection<Card>> 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()
/**
* <p>
* setUnblocked.
* </p>
*/
public final void setUnblockedAttackers() {
final List<AttackingBand> attacking = this.getAttackingBands();
for (final AttackingBand band : attacking) {
band.calculateBlockedState();
if (!band.getBlocked()) {
for (Card attacker : band.getAttackers()) {
// Run Unblocked Trigger
final HashMap<String, Object> runParams = new HashMap<String, Object>();
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<AttackingBand> abs : attackedEntities.values()) {
for(AttackingBand ab : abs) {
Collection<Card> 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<String, Object> runParams = new HashMap<String, Object>();
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<Card> attackers = this.blockerDamageAssignmentOrder.get(blocker);
List<Card> 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<Card, Integer> 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<Card, Integer> map = assigningPlayer.getController().assignCombatDamage(attacker, blockers, damageDealt, defender, this.getAttackingPlayer() != assigningPlayer);
@@ -711,7 +520,7 @@ public class Combat {
final HashMap<GameEntity, List<Card>> wasDamaged = new HashMap<GameEntity, List<Card>>();
for (final Entry<Card, Integer> 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<Card> getUnblockedAttackers() {
ArrayList<Card> unblocked = new ArrayList<Card>();
for (AttackingBand band : this.attackingBands) {
if (!band.getBlocked()) {
unblocked.addAll(band.getAttackers());
}
}
List<Card> unblocked = new ArrayList<Card>();
for (Collection<AttackingBand> 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<Card> attackers = defenderMap.get(defender);
if (attackers != null && !attackers.isEmpty())
public boolean isPlayerAttacked(Player who) {
for(Entry<GameEntity, Collection<AttackingBand>> 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<Card> blockers : blockedBands.values())
if (blockers.contains(blocker))
return true;
return false;
}
public boolean isBlocking(Card blocker, Card attacker) {
AttackingBand ab = getBandOfAttacker(attacker);
Collection<Card> blockers = blockedBands.get(ab);
return blockers != null && blockers.contains(blocker);
}
} // Class Combat

View File

@@ -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<Card> 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<Card> 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<Card> blockers = defending.getCreaturesInPlay();
final List<Card> defendersArmy = defending.getCreaturesInPlay();
final List<Card> attackers = combat.getAttackers();
final List<Card> 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<Card> possibleBlockers = CardLists.filter(defending.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
final List<Card> 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<Card> 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<Card> 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
/**
* <p>
@@ -762,6 +764,9 @@ public class CombatUtil {
}
public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount) {
if( amount == 0 )
return false; // no block
List<String> 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<StaticAbility> 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<String, Object> runParams = new HashMap<String, Object>();
runParams.put("Attacker", c);
final List<Card> otherAttackers = game.getCombat().getAttackers();
final List<Card> 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<Card> list = AbilityUtils.filterListByType(opponent.getCardsIn(ZoneType.Battlefield), "Permanent", this);
final List<Card> list = opponent.getCardsIn(ZoneType.Battlefield);
List<Card> 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<Card> cl) {
public static void checkDeclareBlockers(Game game, final List<Card> 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<String, Object> runParams = new HashMap<String, Object>();
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<Card> 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

View File

@@ -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<Card> 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<GameEntity, Card> attackersMap = new HashMapOfLists<GameEntity, Card>(CollectionSuppliers.<Card>arrayLists());
for(GameEntity ge : game.getCombat().getDefenders()) attackersMap.addAll(ge, game.getCombat().getAttackersOf(ge));
game.fireEvent(new GameEventAttackersDeclared(playerTurn, attackersMap));
// This Exalted handler should be converted to script
if (game.getCombat().getAttackers().size() == 1) {
final Player attackingPlayer = game.getCombat().getAttackingPlayer();
final Card attacker = game.getCombat().getAttackers().get(0);
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<String, Object> runParams = new HashMap<String, Object>();
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<GameEntity, Card> attackersMap = new HashMapOfLists<GameEntity, Card>(CollectionSuppliers.<Card>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<String, Object> runParams = new HashMap<String, Object>();
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<Card> 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<GameEntity, MapOfLists<Card, Card>> blockers = new HashMap<GameEntity, MapOfLists<Card,Card>>();
for(GameEntity ge : game.getCombat().getDefenders()) {
for(GameEntity ge : combat.getDefenders()) {
MapOfLists<Card, Card> protectThisDefender = new HashMapOfLists<Card, Card>(CollectionSuppliers.<Card>arrayLists());
for(Card att : game.getCombat().getAttackersOf(ge)) {
protectThisDefender.addAll(att, game.getCombat().getBlockers(att));
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;
}

View File

@@ -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<Card> 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<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard);

View File

@@ -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<Card> orderBlockers(Card attacker, List<Card> blockers) {
return ComputerUtilBlock.orderBlockers(attacker, blockers);
return AiBlockController.orderBlockers(attacker, blockers);
}
@Override
public List<Card> orderAttackers(Card blocker, List<Card> 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

View File

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

View File

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

View File

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

View File

@@ -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<Card, List<Card>> allBlocking = new HashMap<Card, List<Card>>();
// 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<Card>());
}
List<Card> 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();
}
}

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

@@ -28,7 +28,7 @@ public class EnumMapOfLists<K extends Enum<K>, V> extends EnumMap<K, Collection<
this.factory = factory;
}
private Collection<V> ensureCollectionFor(K key) {
public Collection<V> ensureCollectionFor(K key) {
Collection<V> value = get(key);
if ( value == null ) {
value = factory.get();

View File

@@ -32,7 +32,7 @@ public class HashMapOfLists<K, V> extends HashMap<K, Collection<V>> implements M
private static final long serialVersionUID = 3029089910183132930L;
private Collection<V> ensureCollectionFor(K key) {
public Collection<V> ensureCollectionFor(K key) {
Collection<V> value = get(key);
if ( value == null ) {
value = factory.get();

View File

@@ -6,4 +6,5 @@ import java.util.Map;
public interface MapOfLists<K, V> extends Map<K, Collection<V>> {
void add(K key, V element);
void addAll(K key, Collection<V> element);
Collection<V> ensureCollectionFor(K key);
}

View File

@@ -33,7 +33,7 @@ public class TreeMapOfLists<K, V> extends TreeMap<K, Collection<V>> implements M
this.factory = factory;
}
private Collection<V> ensureCollectionFor(K key) {
public Collection<V> ensureCollectionFor(K key) {
Collection<V> value = get(key);
if ( value == null ) {
value = factory.get();

View File

@@ -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()) {