diff --git a/forge-adventure/src/main/java/forge/adventure/Main.java b/forge-adventure/src/main/java/forge/adventure/Main.java
index cf858088814..1c8a7994541 100644
--- a/forge-adventure/src/main/java/forge/adventure/Main.java
+++ b/forge-adventure/src/main/java/forge/adventure/Main.java
@@ -37,6 +37,7 @@ import java.nio.file.Paths;
isPortraitMode = true;
totalDeviceRAM = 0;
GuiBase.setDeviceInfo("", "", 0, 0);
+ GuiBase.setIsAdventureMode(true);
}
diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java
index 94e600d655e..cbeeaaea18d 100644
--- a/forge-ai/src/main/java/forge/ai/AiAttackController.java
+++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java
@@ -78,7 +78,6 @@ public class AiAttackController {
private int aiAggression = 0; // added by Masher, how aggressive the ai is attack will be depending on circumstances
private final boolean nextTurn;
-
/**
*
* Constructor for ComputerUtil_Attack2.
@@ -358,7 +357,7 @@ public class AiAttackController {
opponentsAttackers = CardLists.filter(opponentsAttackers, new Predicate() {
@Override
public boolean apply(final Card c) {
- return ComputerUtilCombat.canAttackNextTurn(c) && c.getNetCombatDamage() > 0;
+ return c.getNetCombatDamage() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
}
});
for (final Card c : this.myList) {
@@ -422,7 +421,7 @@ public class AiAttackController {
int humanBasePower = ComputerUtilCombat.getAttack(this.oppList.get(0)) + humanExaltedBonus;
if (finestHour) {
// For Finest Hour, one creature could attack and get the bonus TWICE
- humanBasePower = humanBasePower + humanExaltedBonus;
+ humanBasePower += humanExaltedBonus;
}
final int totalExaltedAttack = opp.isCardInPlay("Rafiq of the Many") ? 2 * humanBasePower
: humanBasePower;
@@ -470,7 +469,7 @@ public class AiAttackController {
if (totalAttack > 0 && ai.getLife() <= totalAttack && !ai.cantLoseForZeroOrLessLife()) {
return true;
}
- return ai.canReceiveCounters(CounterEnumType.POISON) && ai.getPoisonCounters() + totalPoison > 9;
+ return ai.getPoisonCounters() + totalPoison > 9 && ai.canReceiveCounters(CounterEnumType.POISON);
}
private boolean doAssault(final Player ai) {
@@ -672,9 +671,9 @@ public class AiAttackController {
*
* @return a {@link forge.game.combat.Combat} object.
*/
- public final void declareAttackers(final Combat combat) {
+ public final int declareAttackers(final Combat combat) {
if (this.attackers.isEmpty()) {
- return;
+ return aiAggression;
}
// Aggro options
@@ -684,14 +683,18 @@ public class AiAttackController {
int extraChanceIfOppHasMana = 0;
boolean tradeIfLowerLifePressure = false;
boolean predictEvasion = false;
+ boolean simAI = false;
if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
- playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO);
- chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE);
- tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
- extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA);
- tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
- predictEvasion = aic.getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION);
+ simAI = aic.usesSimulation();
+ if (!simAI) {
+ playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO);
+ chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE);
+ tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT);
+ extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA);
+ tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE);
+ predictEvasion = aic.getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION);
+ }
}
final boolean bAssault = doAssault(ai);
@@ -714,7 +717,7 @@ public class AiAttackController {
if (attackMax == 0) {
// can't attack anymore
- return;
+ return aiAggression;
}
// Attackers that don't really have a choice
@@ -751,7 +754,7 @@ public class AiAttackController {
}
}
if (attackersLeft.isEmpty()) {
- return;
+ return aiAggression;
}
}
@@ -761,7 +764,7 @@ public class AiAttackController {
}
// Revenge of Ravens: make sure the AI doesn't kill itself and doesn't damage itself unnecessarily
if (!doRevengeOfRavensAttackLogic(ai, defender, attackersLeft, numForcedAttackers, attackMax)) {
- return;
+ return aiAggression;
}
if (bAssault && defender == this.defendingOpponent) { // in case we are forced to attack someone else
@@ -771,14 +774,14 @@ public class AiAttackController {
for (Card attacker : attackersLeft) {
// reached max, breakup
if (attackMax != -1 && combat.getAttackers().size() >= attackMax)
- return;
+ return aiAggression;
if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) {
combat.addAttacker(attacker, defender);
}
}
// no more creatures to attack
- return;
+ return aiAggression;
}
// Cards that are remembered to attack anyway (e.g. temporarily stolen creatures)
@@ -816,7 +819,7 @@ public class AiAttackController {
for (Card attacker : this.attackers) {
if (canAttackWrapper(attacker, defender) && shouldAttack(ai, attacker, this.blockers, combat, defender)) {
combat.addAttacker(attacker, defender);
- return;
+ return aiAggression;
}
}
}
@@ -835,7 +838,19 @@ public class AiAttackController {
}
}
// no more creatures to attack
- return;
+ return aiAggression;
+ }
+
+ if (simAI && ai.isCardInPlay("Reconnaissance")) {
+ for (Card attacker : attackersLeft) {
+ if (canAttackWrapper(attacker, defender)) {
+ // simulation will decide if attacker stays in combat based on blocks
+ combat.addAttacker(attacker, defender);
+ }
+ }
+ // safe to exert
+ this.aiAggression = 6;
+ return aiAggression;
}
// *******************
@@ -877,7 +892,7 @@ public class AiAttackController {
for (final Card pCard : categorizedOppList) {
// if the creature can attack next turn add it to counter attackers list
- if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) {
+ if (pCard.getNetCombatDamage() > 0 && ComputerUtilCombat.canAttackNextTurn(pCard)) {
nextTurnAttackers.add(pCard);
candidateCounterAttackDamage += pCard.getNetCombatDamage();
humanForces += 1; // player forces they might use to attack
@@ -1108,6 +1123,8 @@ public class AiAttackController {
defender = pwNearUlti != null ? pwNearUlti : ComputerUtilCard.getBestPlaneswalkerAI(pwDefending);
}
}
+
+ return aiAggression;
}
/**
@@ -1133,8 +1150,7 @@ public class AiAttackController {
int numberOfPossibleBlockers = 0;
// Is it a creature that has a more valuable ability with a tap cost than what it can do by attacking?
- if ((attacker.hasSVar("NonCombatPriority"))
- && (!attacker.hasKeyword(Keyword.VIGILANCE))) {
+ if (attacker.hasSVar("NonCombatPriority") && !attacker.hasKeyword(Keyword.VIGILANCE)) {
// For each level of priority, enemy has to have life as much as the creature's power
// so a priority of 4 means the creature will not attack unless it can defeat that player in 4 successful attacks.
// the lower the priroity, the less willing the AI is to use the creature for attacking.
@@ -1159,8 +1175,7 @@ public class AiAttackController {
}
boolean hasAttackEffect = attacker.getSVar("HasAttackEffect").equals("TRUE") || attacker.hasStartOfKeyword("Annihilator");
// is there a gain in attacking even when the blocker is not killed (Lifelink, Wither,...)
- boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE")
- || "Blocked".equals(attacker.getSVar("HasAttackEffect"));
+ boolean hasCombatEffect = attacker.getSVar("HasCombatEffect").equals("TRUE") || "Blocked".equals(attacker.getSVar("HasAttackEffect"));
// contains only the defender's blockers that can actually block the attacker
CardCollection validBlockers = CardLists.filter(defenders, new Predicate() {
@@ -1245,7 +1260,7 @@ public class AiAttackController {
canBeKilledByOne = true;
isWorthLessThanAllKillers = false;
hasCombatEffect = false;
- } else if ((canKillAllDangerous || !canBeKilled) && ComputerUtilCard.canBeBlockedProfitably(defendingOpponent, attacker)) {
+ } else if ((canKillAllDangerous || !canBeKilled) && ComputerUtilCard.canBeBlockedProfitably(defendingOpponent, attacker, true)) {
canKillAllDangerous = false;
canBeKilled = true;
}
@@ -1318,7 +1333,7 @@ public class AiAttackController {
return false; // don't attack
}
- public static List exertAttackers(List attackers) {
+ public static List exertAttackers(List attackers, int aggression) {
List exerters = Lists.newArrayList();
for (Card c : attackers) {
boolean shouldExert = false;
@@ -1362,9 +1377,14 @@ public class AiAttackController {
continue;
}
+ if (!shouldExert) {
+ // TODO Improve when the AI wants to use Exert powers
+ shouldExert = aggression > 3;
+ }
+
// A specific AI condition for Exert: if specified on the card, the AI will always
// exert creatures that meet this condition
- if (c.hasSVar("AIExertCondition")) {
+ if (!shouldExert && c.hasSVar("AIExertCondition")) {
if (!c.getSVar("AIExertCondition").isEmpty()) {
final String needsToExert = c.getSVar("AIExertCondition");
String sVar = needsToExert.split(" ")[0];
@@ -1379,11 +1399,6 @@ public class AiAttackController {
}
}
- if (!shouldExert && MyRandom.getRandom().nextBoolean()) {
- // TODO Improve when the AI wants to use Exert powers
- shouldExert = true;
- }
-
if (shouldExert) {
exerters.add(c);
}
diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java
index 979bf8a959d..63f7b7cbac2 100644
--- a/forge-ai/src/main/java/forge/ai/AiBlockController.java
+++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java
@@ -73,7 +73,11 @@ public class AiBlockController {
private boolean lifeInDanger = false;
- public AiBlockController(Player aiPlayer) {
+ // set to true when AI is predicting a blocking for another player so it doesn't use hidden information
+ private boolean checkingOther = false;
+
+ public AiBlockController(Player aiPlayer, boolean checkingOther) {
+ this.checkingOther = checkingOther;
ai = aiPlayer;
}
@@ -295,7 +299,7 @@ public class AiBlockController {
combat.addBlocker(attacker, blocker);
}
}
- attackersLeft = (new ArrayList<>(currentAttackers));
+ attackersLeft = new ArrayList<>(currentAttackers);
// 6. Blockers that don't survive until the next turn anyway
for (final Card attacker : attackersLeft) {
@@ -322,7 +326,7 @@ public class AiBlockController {
combat.addBlocker(attacker, blocker);
}
}
- attackersLeft = (new ArrayList<>(currentAttackers));
+ attackersLeft = new ArrayList<>(currentAttackers);
}
private Predicate rampagesOrNeedsManyToBlock(final Combat combat) {
@@ -421,7 +425,7 @@ public class AiBlockController {
}
}
- attackersLeft = (new ArrayList<>(currentAttackers));
+ attackersLeft = new ArrayList<>(currentAttackers);
currentAttackers = new ArrayList<>(attackersLeft);
boolean considerTripleBlock = true;
@@ -457,7 +461,7 @@ public class AiBlockController {
return false;
}
final boolean randomTrade = wouldLikeToRandomlyTrade(attacker, c, combat);
- return lifeInDanger || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker) || randomTrade;
+ return lifeInDanger || randomTrade || ComputerUtilCard.evaluateCreature(c) + diff < ComputerUtilCard.evaluateCreature(attacker);
}
});
if (usableBlockers.size() < 2) {
@@ -560,7 +564,7 @@ public class AiBlockController {
}
}
- attackersLeft = (new ArrayList<>(currentAttackers));
+ attackersLeft = new ArrayList<>(currentAttackers);
}
private void makeGangNonLethalBlocks(final Combat combat) {
@@ -608,7 +612,7 @@ public class AiBlockController {
}
}
- attackersLeft = (new ArrayList<>(currentAttackers));
+ attackersLeft = new ArrayList<>(currentAttackers);
}
// Bad Trade Blocks (should only be made if life is in danger)
@@ -653,7 +657,7 @@ public class AiBlockController {
}
}
}
- attackersLeft = (new ArrayList<>(currentAttackers));
+ attackersLeft = new ArrayList<>(currentAttackers);
}
// Chump Blocks (should only be made if life is in danger)
@@ -854,7 +858,7 @@ public class AiBlockController {
}
private void makeChumpBlocksToSavePW(Combat combat) {
- if (ComputerUtilCombat.lifeInDanger(ai, combat) || ai.getLife() <= ai.getStartingLife() / 5) {
+ if (ai.getLife() <= ai.getStartingLife() / 5 || ComputerUtilCombat.lifeInDanger(ai, combat)) {
// most likely not worth trying to protect planeswalkers when at threateningly low life or in
// dangerous combat which threatens lethal or severe damage to face
return;
@@ -1045,7 +1049,7 @@ public class AiBlockController {
makeGangBlocks(combat);
// When the AI holds some Fog effect, don't bother about lifeInDanger
- if (!ComputerUtil.hasAFogEffect(ai)) {
+ if (!ComputerUtil.hasAFogEffect(ai, checkingOther)) {
lifeInDanger = ComputerUtilCombat.lifeInDanger(ai, combat);
makeTradeBlocks(combat); // choose necessary trade blocks
@@ -1145,7 +1149,7 @@ public class AiBlockController {
// check to see if it's possible to defend a Planeswalker under attack with a chump block,
// unless life is low enough to be more worried about saving preserving the life total
- if (ai.getController().isAI() && !ComputerUtilCombat.lifeInDanger(ai, combat)) {
+ if (ai.getController().isAI()) {
makeChumpBlocksToSavePW(combat);
}
@@ -1206,7 +1210,7 @@ public class AiBlockController {
final CardCollection result = new CardCollection();
boolean newBlockerIsAdded = false;
// The new blocker comes right after this one
- final Card newBlockerRightAfter = (newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1));
+ final Card newBlockerRightAfter = newBlockerIndex == 0 ? null : allBlockers.get(newBlockerIndex - 1);
if (newBlockerRightAfter == null
&& damage >= ComputerUtilCombat.getEnoughDamageToKill(blocker, damage, attacker, true)) {
result.add(blocker);
@@ -1274,16 +1278,19 @@ public class AiBlockController {
int oppCreatureCount = 0;
if (ai.getController().isAI()) {
AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
- enableRandomTrades = aic.getBooleanProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK);
- randomTradeIfBehindOnBoard = aic.getBooleanProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS);
- randomTradeIfCreatInHand = aic.getBooleanProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT);
- minRandomTradeChance = aic.getIntProperty(AiProps.MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
- maxRandomTradeChance = aic.getIntProperty(AiProps.MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
- chanceModForEmbalm = aic.getIntProperty(AiProps.CHANCE_DECREASE_TO_TRADE_VS_EMBALM);
- maxCreatDiff = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE);
- maxCreatDiffWithRepl = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL);
- chanceToTradeToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER);
- chanceToTradeDownToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER);
+ // simulation must get same results or it may crash
+ if (!aic.usesSimulation()) {
+ enableRandomTrades = aic.getBooleanProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK);
+ randomTradeIfBehindOnBoard = aic.getBooleanProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS);
+ randomTradeIfCreatInHand = aic.getBooleanProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT);
+ minRandomTradeChance = aic.getIntProperty(AiProps.MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
+ maxRandomTradeChance = aic.getIntProperty(AiProps.MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK);
+ chanceModForEmbalm = aic.getIntProperty(AiProps.CHANCE_DECREASE_TO_TRADE_VS_EMBALM);
+ maxCreatDiff = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE);
+ maxCreatDiffWithRepl = aic.getIntProperty(AiProps.MAX_DIFF_IN_CREATURE_COUNT_TO_TRADE_WITH_REPL);
+ chanceToTradeToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_TO_SAVE_PLANESWALKER);
+ chanceToTradeDownToSaveWalker = aic.getIntProperty(AiProps.CHANCE_TO_TRADE_DOWN_TO_SAVE_PLANESWALKER);
+ }
}
if (!enableRandomTrades) {
@@ -1320,7 +1327,7 @@ public class AiBlockController {
chance = Math.max(0, chance - chanceModForEmbalm);
}
- if (blocker.isFaceDown() && blocker.getState(CardStateName.Original).getType().isCreature()) {
+ if (blocker.isFaceDown() && !checkingOther && blocker.getState(CardStateName.Original).getType().isCreature()) {
// if the blocker is a face-down creature (e.g. cast via Morph, Manifest), evaluate it
// in relation to the original state, not to the Morph state
evalBlk = ComputerUtilCard.evaluateCreature(Card.fromPaperCard(blocker.getPaperCard(), ai), false, true);
@@ -1329,7 +1336,7 @@ public class AiBlockController {
boolean powerParityOrHigher = blocker.getNetPower() <= attacker.getNetPower();
boolean creatureParityOrAllowedDiff = aiCreatureCount
+ (randomTradeIfBehindOnBoard ? maxCreatDiff : 0) >= oppCreatureCount;
- boolean wantToTradeWithCreatInHand = randomTradeIfCreatInHand
+ boolean wantToTradeWithCreatInHand = !checkingOther && randomTradeIfCreatInHand
&& Iterables.any(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.CREATURES)
&& aiCreatureCount + maxCreatDiffWithRepl >= oppCreatureCount;
boolean wantToSavePlaneswalker = MyRandom.percentTrue(chanceToSavePW)
diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java
index 9777d7f9bf2..fc43bbf1bd0 100644
--- a/forge-ai/src/main/java/forge/ai/AiController.java
+++ b/forge-ai/src/main/java/forge/ai/AiController.java
@@ -88,6 +88,7 @@ public class AiController {
private boolean cheatShuffle;
private boolean useSimulation;
private SpellAbilityPicker simPicker;
+ private int lastAttackAggression;
public boolean canCheatShuffle() {
return cheatShuffle;
@@ -104,6 +105,10 @@ public class AiController {
this.useSimulation = value;
}
+ public int getAttackAggression() {
+ return lastAttackAggression;
+ }
+
public SpellAbilityPicker getSimulationPicker() {
return simPicker;
}
@@ -294,8 +299,9 @@ public class AiController {
// These checks only work if the Executing SpellAbility is an Ability_Sub.
if (exSA instanceof AbilitySub && !doTrigger(exSA, false)) {
// AI would not run this trigger if given the chance
- if (api == null && card.isCreature() && exSA.usesTargeting() && !exSA.getTargetRestrictions().hasCandidates(exSA) && ComputerUtil.aiLifeInDanger(activatingPlayer, true, 0)) {
- // trigger will not run due to lack of targets and we desperately need a creature
+ if (api == null && card.isCreature() && !ComputerUtilAbility.isFullyTargetable(exSA) &&
+ (ComputerUtil.aiLifeInDanger(activatingPlayer, true, 0) || "BadETB".equals(tr.getParam("AILogic")))) {
+ // trigger will not run due to lack of targets and we 1. desperately need a creature or 2. are happy about that
continue;
}
return false;
@@ -651,13 +657,12 @@ public class AiController {
return null;
}
- public boolean reserveManaSourcesForNextSpell(SpellAbility sa, SpellAbility exceptForSa) {
- return reserveManaSources(sa, null, false, true, exceptForSa);
- }
-
public boolean reserveManaSources(SpellAbility sa) {
return reserveManaSources(sa, PhaseType.MAIN2, false, false, null);
}
+ public boolean reserveManaSourcesForNextSpell(SpellAbility sa, SpellAbility exceptForSa) {
+ return reserveManaSources(sa, null, false, true, exceptForSa);
+ }
public boolean reserveManaSources(SpellAbility sa, PhaseType phaseType, boolean enemy) {
return reserveManaSources(sa, phaseType, enemy, true, null);
}
@@ -737,7 +742,7 @@ public class AiController {
}
int oldCMC = -1;
- boolean xCost = sa.getPayCosts().hasXInAnyCostPart() || sa.getHostCard().hasStartOfKeyword("Strive");
+ boolean xCost = sa.costHasX() || sa.getHostCard().hasStartOfKeyword("Strive");
if (!xCost) {
if (!ComputerUtilCost.canPayCost(sa, player)) {
// for most costs, it's OK to check if they can be paid early in order to avoid running a heavy API check
@@ -751,14 +756,15 @@ public class AiController {
}
// state needs to be switched here so API checks evaluate the right face
- if (sa.getCardState() != null && !sa.getHostCard().isInPlay() && sa.getCardState().getStateName() == CardStateName.Modal) {
- sa.getHostCard().setState(CardStateName.Modal, false);
+ CardStateName currentState = sa.getCardState() != null && sa.getHostCard().getCurrentStateName() != sa.getCardState().getStateName() && !sa.getHostCard().isInPlay() ? sa.getHostCard().getCurrentStateName() : null;
+ if (currentState != null) {
+ sa.getHostCard().setState(sa.getCardState().getStateName(), false);
}
AiPlayDecision canPlay = canPlaySa(sa); // this is the "heaviest" check, which also sets up targets, defines X, etc.
- if (sa.getCardState() != null && !sa.getHostCard().isInPlay() && sa.getCardState().getStateName() == CardStateName.Modal) {
- sa.getHostCard().setState(CardStateName.Original, false);
+ if (currentState != null) {
+ sa.getHostCard().setState(currentState, false);
}
if (canPlay != AiPlayDecision.WillPlay) {
@@ -900,7 +906,7 @@ public class AiController {
return canPlayFromEffectAI((SpellPermanent)sa, false, true);
}
if (sa.usesTargeting()) {
- if (!sa.isTargetNumberValid() && !sa.getTargetRestrictions().hasCandidates(sa)) {
+ if (!sa.isTargetNumberValid() && sa.getTargetRestrictions().getNumCandidates(sa, true) == 0) {
return AiPlayDecision.TargetingFailed;
}
if (!StaticAbilityMustTarget.meetsMustTargetRestriction(sa)) {
@@ -1436,7 +1442,7 @@ public class AiController {
// declares blockers for given defender in a given combat
public void declareBlockersFor(Player defender, Combat combat) {
- AiBlockController block = new AiBlockController(defender);
+ AiBlockController block = new AiBlockController(defender, defender != player);
// When player != defender, AI should declare blockers for its benefit.
block.assignBlockersForCombat(combat);
}
@@ -1444,7 +1450,7 @@ public class AiController {
public void declareAttackers(Player attacker, Combat combat) {
// 12/2/10(sol) the decision making here has moved to getAttackers()
AiAttackController aiAtk = new AiAttackController(attacker);
- aiAtk.declareAttackers(combat);
+ lastAttackAggression = aiAtk.declareAttackers(combat);
// if invalid: just try an attack declaration that we know to be legal
if (!CombatUtil.validateAttackers(combat)) {
@@ -1621,6 +1627,7 @@ public class AiController {
Map params = t.getMapParams();
if ("ChangesZone".equals(params.get("Mode"))
&& params.containsKey("ValidCard")
+ && (!params.containsKey("AILogic") || !params.get("AILogic").equals("SafeToHold"))
&& !params.get("ValidCard").contains("nonLand")
&& ((params.get("ValidCard").contains("Land")) || (params.get("ValidCard").contains("Permanent")))
&& "Battlefield".equals(params.get("Destination"))) {
@@ -2014,8 +2021,7 @@ public class AiController {
Card bestCreature = ComputerUtilCard.getBestCreatureAI(rightToughness.isEmpty() ? pool : rightToughness);
if (bestCreature != null) {
result.add(bestCreature);
- } else {
- result.add(Aggregates.random(pool)); // should ideally never get here
+ break;
}
} else {
CardCollectionView viableOptions = CardLists.filter(pool, Predicates.and(CardPredicates.isControlledByAnyOf(sa.getActivatingPlayer().getOpponents())),
@@ -2026,22 +2032,22 @@ public class AiController {
}
});
Card best = ComputerUtilCard.getBestAI(viableOptions);
- if (best == null) {
- best = Aggregates.random(pool); // should ideally never get here either
+ if (best != null) {
+ result.add(best);
+ break;
}
- result.add(best);
}
+ result.add(Aggregates.random(pool)); // should ideally never get here
break;
default:
CardCollection editablePool = new CardCollection(pool);
for (int i = 0; i < max; i++) {
Card c = player.getController().chooseSingleEntityForEffect(editablePool, sa, null, isOptional, params);
- if (c != null) {
- result.add(c);
- editablePool.remove(c);
- } else {
+ if (c == null) {
break;
}
+ result.add(c);
+ editablePool.remove(c);
// Special case for Bow to My Command which simulates a complex tap cost via ChooseCard
// TODO: consider enhancing support for tapXType in UnlessCost to get rid of this hack
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
index 4d43e0f39d4..e59039c30cf 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
@@ -137,6 +137,9 @@ public class ComputerUtil {
}
if (chooseTargets != null) {
chooseTargets.run();
+ if (!sa.isTargetNumberValid()) {
+ return false;
+ }
}
final Cost cost = sa.getPayCosts();
@@ -195,7 +198,7 @@ public class ComputerUtil {
// Consider the costs here for relative "scoring"
if (hasDiscardHandCost(cost)) {
// Null Brooch aid
- restrict -= (ai.getCardsIn(ZoneType.Hand).size() * 20);
+ restrict -= ai.getCardsIn(ZoneType.Hand).size() * 20;
}
// Abilities before Spells (card advantage)
@@ -234,7 +237,7 @@ public class ComputerUtil {
// (Spell,Ability,Triggered)
final String tgtType = sa.getParam("TargetType");
if (tgtType != null) {
- restrict -= (5 * tgtType.split(",").length);
+ restrict -= 5 * tgtType.split(",").length;
}
return restrict;
}
@@ -1320,7 +1323,7 @@ public class ComputerUtil {
} else if (sa.isPwAbility() && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
for (final CostPart part : abCost.getCostParts()) {
if (part instanceof CostPutCounter) {
- return true;
+ return part.convertAmount() == null || part.convertAmount() > 0 || ai.isCardInPlay("Carth the Lion");
}
}
}
@@ -1442,11 +1445,13 @@ public class ComputerUtil {
return false;
}
- public static boolean hasAFogEffect(final Player ai) {
+ public static boolean hasAFogEffect(final Player ai, boolean checkingOther) {
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
all.addAll(ai.getCardsActivableInExternalZones(true));
- all.addAll(ai.getCardsIn(ZoneType.Hand));
+ if (!checkingOther || ai.hasKeyword("Play with your hand revealed.")) {
+ all.addAll(ai.getCardsIn(ZoneType.Hand));
+ }
for (final Card c : all) {
for (final SpellAbility sa : c.getSpellAbilities()) {
@@ -1457,6 +1462,10 @@ public class ComputerUtil {
// Avoid re-entry for cards already being considered (e.g. in case the AI is considering
// Convoke or Improvise for a Fog-like effect)
if (c.hasKeyword("Convoke") || c.hasKeyword("Improvise")) {
+ // TODO skipping for now else this will lead to GUI interaction
+ if (!c.getController().isAI()) {
+ continue;
+ }
if (AiCardMemory.isRememberedCard(ai, c, AiCardMemory.MemorySet.MARKED_TO_AVOID_REENTRY)) {
continue;
}
@@ -1483,6 +1492,7 @@ public class ComputerUtil {
if (sa.getApi() != ApiType.DealDamage) {
continue;
}
+ sa.setActivatingPlayer(ai);
final String numDam = sa.getParam("NumDmg");
int dmg = AbilityUtils.calculateAmount(sa.getHostCard(), numDam, sa);
if (dmg <= damage) {
@@ -1504,7 +1514,7 @@ public class ComputerUtil {
}
// Triggered abilities
- if (c.isCreature() && c.isInZone(ZoneType.Battlefield) && CombatUtil.canAttack(c)) {
+ if (c.isCreature() && c.isInPlay() && CombatUtil.canAttack(c)) {
for (final Trigger t : c.getTriggers()) {
if (TriggerType.Attacks.equals(t.getMode())) {
SpellAbility sa = t.ensureAbility();
@@ -1738,7 +1748,7 @@ public class ComputerUtil {
if (o instanceof Card) {
final Card c = (Card) o;
final boolean canRemove = (c.getNetToughness() <= dmg)
- || (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && (dmg >= ComputerUtilCombat.getDamageToKill(c, false)));
+ || (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && dmg >= ComputerUtilCombat.getDamageToKill(c, false));
if (!canRemove) {
continue;
}
@@ -1760,7 +1770,7 @@ public class ComputerUtil {
}
if (saviourApi == ApiType.Protection) {
- if (!topStack.usesTargeting() || (ProtectAi.toProtectFrom(source, saviour) == null)) {
+ if (!topStack.usesTargeting() || ProtectAi.toProtectFrom(source, saviour) == null) {
continue;
}
}
@@ -1803,7 +1813,7 @@ public class ComputerUtil {
}
}
if (saviourApi == ApiType.Protection) {
- if (!topStack.usesTargeting() || (ProtectAi.toProtectFrom(source, saviour) == null)) {
+ if (!topStack.usesTargeting() || ProtectAi.toProtectFrom(source, saviour) == null) {
continue;
}
}
@@ -1836,7 +1846,7 @@ public class ComputerUtil {
continue;
}
if (saviourApi == ApiType.Protection) {
- if (!topStack.usesTargeting() || (ProtectAi.toProtectFrom(source, saviour) == null)) {
+ if (!topStack.usesTargeting() || ProtectAi.toProtectFrom(source, saviour) == null) {
continue;
}
}
@@ -1860,11 +1870,11 @@ public class ComputerUtil {
if (o instanceof Card) {
final Card c = (Card) o;
// give Shroud to targeted creatures
- if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll && !topStack.usesTargeting()) && !grantShroud) {
+ if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) && (!topStack.usesTargeting() || !grantShroud)) {
continue;
}
if (saviourApi == ApiType.Protection) {
- if (!topStack.usesTargeting() || (ProtectAi.toProtectFrom(source, saviour) == null)) {
+ if (!topStack.usesTargeting() || ProtectAi.toProtectFrom(source, saviour) == null) {
continue;
}
}
@@ -1873,7 +1883,9 @@ public class ComputerUtil {
}
}
//Generic curse auras
- else if ((threatApi == ApiType.Attach && (topStack.isCurse() || "Curse".equals(topStack.getParam("AILogic"))))) {
+ else if ((threatApi == ApiType.Attach && (topStack.isCurse() || "Curse".equals(topStack.getParam("AILogic"))))
+ && (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
+ || saviourApi == ApiType.Protection || saviourApi == null)) {
AiController aic = aiPlayer.isAI() ? ((PlayerControllerAi)aiPlayer.getController()).getAi() : null;
boolean enableCurseAuraRemoval = aic != null ? aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE) : false;
if (enableCurseAuraRemoval) {
@@ -1881,11 +1893,11 @@ public class ComputerUtil {
if (o instanceof Card) {
final Card c = (Card) o;
// give Shroud to targeted creatures
- if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll && !topStack.usesTargeting()) && !grantShroud) {
+ if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) && (!topStack.usesTargeting() || !grantShroud)) {
continue;
}
if (saviourApi == ApiType.Protection) {
- if (!topStack.usesTargeting() || (ProtectAi.toProtectFrom(source, saviour) == null)) {
+ if (!topStack.usesTargeting() || ProtectAi.toProtectFrom(source, saviour) == null) {
continue;
}
}
@@ -2045,7 +2057,7 @@ public class ComputerUtil {
}
// otherwise, reject bad hands or return score
- if ( landSize < 2) {
+ if (landSize < 2) {
// BAD Hands, 0 or 1 lands
if (landsInDeck == 0 || library.size()/landsInDeck > 6) {
// Heavy spell deck it's ok
@@ -2451,7 +2463,7 @@ public class ComputerUtil {
return opponent ? "Feather" : "Quill";
}
// if source is not on the battlefield anymore, choose +1/+1 ones
- if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
+ if (!game.getCardState(source).isInPlay()) {
return opponent ? "Feather" : "Quill";
}
// if no hand cards, try to mill opponent
@@ -2483,7 +2495,7 @@ public class ComputerUtil {
}
// if source is not on the battlefield anymore
- if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
+ if (!game.getCardState(source).isInPlay()) {
return opponent ? "Strength" : "Numbers";
}
@@ -2529,7 +2541,7 @@ public class ComputerUtil {
}
// if source is not on the battlefield anymore
- if (!game.getCardState(source).isInZone(ZoneType.Battlefield)) {
+ if (!game.getCardState(source).isInPlay()) {
return opponent ? "Sprout" : "Harvest";
}
// TODO add Lifegain to +1/+1 counters trigger
@@ -2958,8 +2970,8 @@ public class ComputerUtil {
}
};
- int numInHand = CardLists.filter(inHand, markedAsReanimator).size();
- int numInDeck = CardLists.filter(inDeck, markedAsReanimator).size();
+ int numInHand = CardLists.count(inHand, markedAsReanimator);
+ int numInDeck = CardLists.count(inDeck, markedAsReanimator);
return numInHand > 0 || numInDeck >= 3;
}
@@ -3028,7 +3040,7 @@ public class ComputerUtil {
if (!containsAttacker) {
continue;
}
- AiBlockController block = new AiBlockController(ai);
+ AiBlockController block = new AiBlockController(ai, false);
block.assignBlockersForCombat(combat);
// TODO predict other, noncombat sources of damage and add them to the "payment" variable.
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java
index c9aec6c625f..b590b3e23a3 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java
@@ -214,4 +214,15 @@ public class ComputerUtilAbility {
return targeted;
}
+
+ public static boolean isFullyTargetable(SpellAbility sa) {
+ SpellAbility sub = sa;
+ while (sub != null) {
+ if (sub.usesTargeting() && sub.getTargetRestrictions().getNumCandidates(sub, true) < sub.getMinTargets()) {
+ return false;
+ }
+ sub = sub.getSubAbility();
+ }
+ return true;
+ }
}
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
index 9c3ce229079..cea246c9771 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
@@ -109,7 +109,7 @@ public class ComputerUtilCard {
// get biggest Artifact
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
}
-
+
/**
* Returns the best Planeswalker from a given list
* @param list list of cards to evaluate
@@ -211,7 +211,7 @@ public class ComputerUtilCard {
}
});
}
-
+
// get biggest Enchantment
return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc);
}
@@ -229,7 +229,7 @@ public class ComputerUtilCard {
if (land.isEmpty()) {
return null;
}
-
+
// prefer to target non basic lands
final List nbLand = CardLists.filter(land, Predicates.not(CardPredicates.Presets.BASIC_LANDS));
@@ -237,7 +237,7 @@ public class ComputerUtilCard {
// TODO - Rank non basics?
return Aggregates.random(nbLand);
}
-
+
// if no non-basic lands, target the least represented basic land type
String sminBL = "";
int iminBL = Integer.MAX_VALUE;
@@ -257,13 +257,14 @@ public class ComputerUtilCard {
}
return land.get(0);
}
-
+
final List bLand = CardLists.getType(land, sminBL);
for (Card ut : Iterables.filter(bLand, CardPredicates.Presets.UNTAPPED)) {
return ut;
}
-
+
+ // TODO potentially risky if simulation mode currently able to reach this from triggers
return Aggregates.random(bLand); // random tapped land of least represented type
}
@@ -348,7 +349,7 @@ public class ComputerUtilCard {
if (Iterables.isEmpty(all)) {
return null;
}
-
+
// get cheapest card:
Card cheapest = null;
@@ -357,7 +358,7 @@ public class ComputerUtilCard {
cheapest = c;
}
}
-
+
return cheapest;
}
@@ -475,12 +476,12 @@ public class ComputerUtilCard {
if (Iterables.isEmpty(list)) {
return null;
}
-
+
final boolean hasEnchantmants = Iterables.any(list, CardPredicates.Presets.ENCHANTMENTS);
if (biasEnch && hasEnchantmants) {
return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ENCHANTMENTS), null, false);
}
-
+
final boolean hasArtifacts = Iterables.any(list, CardPredicates.Presets.ARTIFACTS);
if (biasArt && hasArtifacts) {
return getCheapestPermanentAI(CardLists.filter(list, CardPredicates.Presets.ARTIFACTS), null, false);
@@ -489,17 +490,17 @@ public class ComputerUtilCard {
if (biasLand && Iterables.any(list, CardPredicates.Presets.LANDS)) {
return getWorstLand(CardLists.filter(list, CardPredicates.Presets.LANDS));
}
-
+
final boolean hasCreatures = Iterables.any(list, CardPredicates.Presets.CREATURES);
if (biasCreature && hasCreatures) {
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
}
-
+
List lands = CardLists.filter(list, CardPredicates.Presets.LANDS);
if (lands.size() > 6) {
return getWorstLand(lands);
}
-
+
if (hasEnchantmants || hasArtifacts) {
final List ae = CardLists.filter(list, Predicates.and(Predicates.or(CardPredicates.Presets.ARTIFACTS, CardPredicates.Presets.ENCHANTMENTS), new Predicate() {
@Override
@@ -509,11 +510,11 @@ public class ComputerUtilCard {
}));
return getCheapestPermanentAI(ae, null, false);
}
-
+
if (hasCreatures) {
return getWorstCreatureAI(CardLists.filter(list, CardPredicates.Presets.CREATURES));
}
-
+
// Planeswalkers fall through to here, lands will fall through if there aren't very many
return getCheapestPermanentAI(list, null, false);
}
@@ -644,7 +645,7 @@ public class ComputerUtilCard {
}
return false;
}
-
+
/**
* Create a mock combat where ai is being attacked and returns the list of likely blockers.
* @param ai blocking player
@@ -652,7 +653,7 @@ public class ComputerUtilCard {
* @return list of creatures assigned to block in the simulation
*/
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
- AiBlockController aiBlk = new AiBlockController(ai);
+ AiBlockController aiBlk = new AiBlockController(ai, false);
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
Combat combat = new Combat(opp);
//Use actual attackers if available, else consider all possible attackers
@@ -675,7 +676,7 @@ public class ComputerUtilCard {
}
return combat.getAllBlockers();
}
-
+
/**
* Decide if a creature is going to be used as a blocker.
* @param ai controller of creature
@@ -692,16 +693,15 @@ public class ComputerUtilCard {
* @param attacker attacking creature to evaluate
* @return attacker will die
*/
- public static boolean canBeBlockedProfitably(final Player ai, Card attacker) {
- AiBlockController aiBlk = new AiBlockController(ai);
+ public static boolean canBeBlockedProfitably(final Player ai, Card attacker, boolean checkingOther) {
+ AiBlockController aiBlk = new AiBlockController(ai, checkingOther);
Combat combat = new Combat(ai);
combat.addAttacker(attacker, ai);
- final List attackers = new ArrayList<>();
- attackers.add(attacker);
+ final List attackers = Lists.newArrayList(attacker);
aiBlk.assignBlockersGivenAttackers(combat, attackers);
return ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, combat);
}
-
+
/**
* getMostExpensivePermanentAI.
*
@@ -711,24 +711,24 @@ public class ComputerUtilCard {
*/
public static Card getMostExpensivePermanentAI(final Iterable all) {
Card biggest = null;
-
+
int bigCMC = -1;
for (final Card card : all) {
// TODO when PlayAi can consider MDFC this should also look at the back face (if not on stack or battlefield)
int curCMC = card.getCMC();
-
+
// Add all cost of all auras with the same controller
if (card.isEnchanted()) {
final List auras = CardLists.filterControlledBy(card.getEnchantedBy(), card.getController());
curCMC += Aggregates.sum(auras, CardPredicates.Accessors.fnGetCmc) + auras.size();
}
-
+
if (curCMC >= bigCMC) {
bigCMC = curCMC;
biggest = card;
}
}
-
+
return biggest;
}
@@ -736,21 +736,21 @@ public class ComputerUtilCard {
if (list.size() == 0) {
return "";
}
-
+
final Map map = Maps.newHashMap();
for (final Card c : list) {
final String name = c.getName();
Integer currentCnt = map.get(name);
map.put(name, currentCnt == null ? Integer.valueOf(1) : Integer.valueOf(1 + currentCnt));
- } // for
-
+ }
+
int max = 0;
String maxName = "";
-
+
for (final Entry entry : map.entrySet()) {
final String type = entry.getKey();
-
+
if (max < entry.getValue()) {
max = entry.getValue();
maxName = type;
@@ -857,7 +857,7 @@ public class ComputerUtilCard {
int max = 0;
String maxType = "";
-
+
for (final Entry entry : typesInDeck.entrySet()) {
final String type = entry.getKey();
@@ -866,7 +866,7 @@ public class ComputerUtilCard {
maxType = type;
}
}
-
+
return maxType;
}
@@ -918,7 +918,7 @@ public class ComputerUtilCard {
return o2.getValue() - o1.getValue();
}
});
-
+
// will this part be once dropped?
List result = new ArrayList<>(cntColors);
for (Pair idx : map) { // fetch color names in the same order
@@ -948,7 +948,7 @@ public class ComputerUtilCard {
Player opp = ai.getWeakestOpponent();
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
-
+
if (logic.equals("MostProminentInHumanDeck")) {
chosen.add(getMostProminentColor(CardLists.filterControlledBy(game.getCardsInGame(), opp), colorChoices));
}
@@ -1047,7 +1047,7 @@ public class ComputerUtilCard {
}
return chosen;
}
-
+
public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) {
final Player ai = sa.getActivatingPlayer();
final AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
@@ -1058,16 +1058,16 @@ public class ComputerUtilCard {
final int costRemoval = sa.getHostCard().getCMC();
final int costTarget = c.getCMC();
-
+
if (!sa.isSpell()) {
return true;
}
-
+
//Check for cards that profit from spells - for example Prowess or Threshold
if (phaseType == PhaseType.MAIN1 && ComputerUtil.castSpellInMain1(ai, sa)) {
return true;
}
-
+
//interrupt 1: Check whether a possible blocker will be killed for the AI to make a bigger attack
if (ph.is(PhaseType.MAIN1) && ph.isPlayerTurn(ai) && c.isCreature()) {
AiAttackController aiAtk = new AiAttackController(ai);
@@ -1107,7 +1107,7 @@ public class ComputerUtilCard {
}
}
}
-
+
// interrupt 3: two for one = good
if (c.isEnchanted()) {
boolean myEnchants = false;
@@ -1121,7 +1121,7 @@ public class ComputerUtilCard {
return true; //card advantage > tempo
}
}
-
+
//interrupt 4: opponent pumping target (only works if the pump target is the chosen best target to begin with)
final MagicStack stack = game.getStack();
if (!stack.isEmpty()) {
@@ -1130,7 +1130,7 @@ public class ComputerUtilCard {
return true;
}
}
-
+
//burn and curse spells
float valueBurn = 0;
if (dmg > 0) {
@@ -1146,7 +1146,7 @@ public class ComputerUtilCard {
return true;
}
}
-
+
//evaluate tempo gain
float valueTempo = Math.max(0.1f * costTarget / costRemoval, valueBurn);
if (c.isEquipped()) {
@@ -1177,7 +1177,7 @@ public class ComputerUtilCard {
if (valueTempo >= 0.8 && ph.getPhase().isBefore(PhaseType.COMBAT_END)) {
return true;
}
-
+
//evaluate threat of targeted card
float threat = 0;
if (c.isCreature()) {
@@ -1260,7 +1260,7 @@ public class ComputerUtilCard {
if (!c.getManaAbilities().isEmpty()) {
threat += 0.5f * costTarget / opp.getLandsInPlay().size(); //set back opponent's mana
}
-
+
final float valueNow = Math.max(valueTempo, threat);
if (valueNow < 0.2) { //hard floor to reduce ridiculous odds for instants over time
return false;
@@ -1296,13 +1296,17 @@ public class ComputerUtilCard {
boolean combatTrick = false;
boolean holdCombatTricks = false;
int chanceToHoldCombatTricks = -1;
+ boolean simAI = false;
if (ai.getController().isAI()) {
- AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
- holdCombatTricks = aic.getBooleanProperty(AiProps.TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
- chanceToHoldCombatTricks = aic.getIntProperty(AiProps.CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
+ AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
+ simAI = aic.usesSimulation();
+ if (!simAI) {
+ holdCombatTricks = aic.getBooleanProperty(AiProps.TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
+ chanceToHoldCombatTricks = aic.getIntProperty(AiProps.CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK);
+ }
}
-
+
if (!c.canBeTargetedBy(sa)) {
return false;
}
@@ -1333,9 +1337,9 @@ public class ComputerUtilCard {
}
// buff attacker/blocker using triggered pump (unless it's lethal and we don't want to be reckless)
- if (immediately && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && !loseCardAtEOT) {
+ if (immediately && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && !loseCardAtEOT) {
if (phase.isPlayerTurn(ai)) {
- if (CombatUtil.canAttack(c)) {
+ if (CombatUtil.canAttack(c) || (game.getCombat() != null && c.isAttacking())) {
return true;
}
} else {
@@ -1349,7 +1353,7 @@ public class ComputerUtilCard {
Card pumped = getPumpedCreature(ai, sa, c, toughness, power, keywords);
List oppCreatures = opp.getCreaturesInPlay();
float chance = 0;
-
+
//create and buff attackers
if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai) && opp.getLife() > 0) {
//1. become attacker for whatever reason
@@ -1385,7 +1389,7 @@ public class ComputerUtilCard {
}
}
}
-
+
//2. grant haste
if (keywords.contains("Haste") && c.hasSickness() && !c.isTapped()) {
double nonCombatChance = 0.0f;
@@ -1405,7 +1409,7 @@ public class ComputerUtilCard {
}
chance += nonCombatChance + combatChance;
}
-
+
//3. grant evasive
if (Iterables.any(oppCreatures, CardPredicates.possibleBlockers(c))) {
if (!Iterables.any(oppCreatures, CardPredicates.possibleBlockers(pumped))
@@ -1414,7 +1418,7 @@ public class ComputerUtilCard {
}
}
}
-
+
//combat trickery
if (phase.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
//clunky code because ComputerUtilCombat.combatantWouldBeDestroyed() does not work for this sort of artificial combat
@@ -1444,7 +1448,7 @@ public class ComputerUtilCard {
pumpedWillDie = true;
}
}
-
+
//1. save combatant
if (ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat) && !pumpedWillDie
&& !c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
@@ -1452,7 +1456,7 @@ public class ComputerUtilCard {
// does not check for Indestructible when computing lethal damage
return true;
}
-
+
//2. kill combatant
boolean survivor = false;
for (Card o : opposing) {
@@ -1477,7 +1481,7 @@ public class ComputerUtilCard {
}
}
}
-
+
//3. buff attacker
if (combat.isAttacking(c) && opp.getLife() > 0) {
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opp, combat, true);
@@ -1551,7 +1555,7 @@ public class ComputerUtilCard {
}
chance += value;
}
-
+
//4. lifelink
if (ai.canGainLife() && ai.getLife() > 0 && !c.hasKeyword(Keyword.LIFELINK) && keywords.contains("Lifelink")
&& (combat.isAttacking(c) || combat.isBlocking(c))) {
@@ -1560,7 +1564,7 @@ public class ComputerUtilCard {
//and trample damage (if any)
chance += 1.0f * dmg / ai.getLife();
}
-
+
//5. if the life of the computer is in danger, try to pump blockers blocking Tramplers
if (combat.isBlocking(c) && toughness > 0 ) {
List blockedBy = combat.getAttackersBlockedBy(c);
@@ -1629,7 +1633,7 @@ public class ComputerUtilCard {
}
}
- return MyRandom.getRandom().nextFloat() < chance;
+ return simAI || MyRandom.getRandom().nextFloat() < chance;
}
/**
@@ -1710,7 +1714,7 @@ public class ComputerUtilCard {
applyStaticContPT(ai.getGame(), pumped, new CardCollection(c));
return pumped;
}
-
+
/**
* Applies static continuous Power/Toughness effects to a (virtual) creature.
* @param game game instance to work with
@@ -1757,7 +1761,7 @@ public class ComputerUtilCard {
}
}
}
-
+
/**
* Evaluate if the ability can save a target against removal
* @param ai casting player
@@ -1871,7 +1875,7 @@ public class ComputerUtilCard {
return oppCards;
}
- CardCollection aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
+ CardCollection aiCreats = ai.getCreaturesInPlay();
if (temporary) {
// Pump effects that add "CARDNAME can't attack" and similar things. Only do it if something is untapped.
oppCards = CardLists.filter(oppCards, CardPredicates.Presets.UNTAPPED);
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
index 8e55739b45c..717e0899a62 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
@@ -160,7 +160,7 @@ public class ComputerUtilCombat {
list = CardLists.filter(list, new Predicate() {
@Override
public boolean apply(final Card c) {
- return CombatUtil.canBlock(att, c) && (c.hasFirstStrike() || c.hasDoubleStrike());
+ return (c.hasFirstStrike() || c.hasDoubleStrike()) && CombatUtil.canBlock(att, c);
}
});
@@ -324,12 +324,12 @@ public class ComputerUtilCombat {
for (final Card attacker : attackers) {
final List blockers = combat.getBlockers(attacker);
- if ((blockers.size() == 0)
+ if (blockers.size() == 0
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage "
+ "as though it weren't blocked.")) {
unblocked.add(attacker);
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
- && (getAttack(attacker) > totalShieldDamage(attacker, blockers))) {
+ && getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
if (!attacker.hasKeyword(Keyword.INFECT)) {
damage += getAttack(attacker) - totalShieldDamage(attacker, blockers);
}
@@ -366,12 +366,12 @@ public class ComputerUtilCombat {
for (final Card attacker : attackers) {
final List blockers = combat.getBlockers(attacker);
- if ((blockers.size() == 0)
+ if (blockers.size() == 0
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage"
+ " as though it weren't blocked.")) {
unblocked.add(attacker);
} else if (attacker.hasKeyword(Keyword.TRAMPLE)
- && (getAttack(attacker) > totalShieldDamage(attacker, blockers))) {
+ && getAttack(attacker) > totalShieldDamage(attacker, blockers)) {
int trampleDamage = getAttack(attacker) - totalShieldDamage(attacker, blockers);
if (attacker.hasKeyword(Keyword.INFECT)) {
poison += trampleDamage;
@@ -431,7 +431,7 @@ public class ComputerUtilCombat {
// check for creatures that must be blocked
final List attackers = combat.getAttackersOf(ai);
- final List threateningCommanders = getLifeThreateningCommanders(ai,combat);
+ final List threateningCommanders = getLifeThreateningCommanders(ai, combat);
for (final Card attacker : attackers) {
final List blockers = combat.getBlockers(attacker);
@@ -457,8 +457,12 @@ public class ComputerUtilCombat {
}
}
- int threshold = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD));
- int maxTreshold = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_MAX_THRESHOLD)) - threshold;
+ int threshold = 0;
+ int maxTreshold = 0;
+ if (ai.getController().isAI()) {
+ threshold = ((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD);
+ maxTreshold = ((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_MAX_THRESHOLD) - threshold;
+ }
int chance = MyRandom.getRandom().nextInt(80) + 5;
while (maxTreshold > 0) {
@@ -617,7 +621,7 @@ public class ComputerUtilCombat {
if (flankingMagnitude >= defender.getNetToughness()) {
return 0;
}
- if (flankingMagnitude >= (defender.getNetToughness() - defender.getDamage())
+ if (flankingMagnitude >= defender.getNetToughness() - defender.getDamage()
&& !defender.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return 0;
}
@@ -1660,7 +1664,7 @@ public class ComputerUtilCombat {
*/
public static boolean combatantCantBeDestroyed(Player ai, final Card combatant) {
// either indestructible or may regenerate
- if (combatant.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, combatant))) {
+ if (combatant.hasKeyword(Keyword.INDESTRUCTIBLE) || ComputerUtil.canRegenerate(ai, combatant)) {
return true;
}
@@ -1715,7 +1719,7 @@ public class ComputerUtilCombat {
if (flankingMagnitude >= blocker.getNetToughness()) {
return false;
}
- if ((flankingMagnitude >= (blocker.getNetToughness() - blocker.getDamage()))
+ if (flankingMagnitude >= blocker.getNetToughness() - blocker.getDamage()
&& !blocker.hasKeyword(Keyword.INDESTRUCTIBLE)) {
return false;
}
@@ -2510,10 +2514,10 @@ public class ComputerUtilCombat {
}
// 2. Otherwise, go through the list of options one by one, choose the first one that can't be blocked profitably.
for (GameEntity p : defenders) {
- if (p instanceof Player && !ComputerUtilCard.canBeBlockedProfitably((Player)p, attacker)) {
+ if (p instanceof Player && !ComputerUtilCard.canBeBlockedProfitably((Player)p, attacker, true)) {
return p;
}
- if (p instanceof Card && !ComputerUtilCard.canBeBlockedProfitably(((Card)p).getController(), attacker)) {
+ if (p instanceof Card && !ComputerUtilCard.canBeBlockedProfitably(((Card)p).getController(), attacker, true)) {
return p;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java
index cce3219276d..fdc52790d6f 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java
@@ -139,7 +139,7 @@ public class ComputerUtilCost {
if (source.getAbilityText().contains("Bloodrush")) {
continue;
} else if (ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN, ai)
- && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize()) {
+ && !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize()) {
// Better do something than just discard stuff
return true;
}
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
index 66b2150eeac..292aa19adf1 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
@@ -299,6 +299,14 @@ public class ComputerUtilMana {
});
saList = filteredList;
break;
+ case "NotSameCard":
+ saList = Lists.newArrayList(Iterables.filter(filteredList, new Predicate() {
+ @Override
+ public boolean apply(final SpellAbility saPay) {
+ return !saPay.getHostCard().getName().equals(sa.getHostCard().getName());
+ }
+ }));
+ break;
default:
break;
}
@@ -363,7 +371,7 @@ public class ComputerUtilMana {
// Exception: when paying generic mana with Cavern of Souls, prefer the colored mana producing ability
// to attempt to make the spell uncounterable when possible.
if (ComputerUtilAbility.getAbilitySourceName(ma).equals("Cavern of Souls")
- && saHost.getType().getCreatureTypes().contains(ma.getHostCard().getChosenType())) {
+ && saHost.getType().hasCreatureType(ma.getHostCard().getChosenType())) {
if (toPay == ManaCostShard.COLORLESS && cost.getUnpaidShards().contains(ManaCostShard.GENERIC)) {
// Deprioritize Cavern of Souls, try to pay generic mana with it instead to use the NoCounter ability
continue;
@@ -1229,15 +1237,14 @@ public class ComputerUtilMana {
return false;
}
- AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
- int chanceToReserve = aic.getIntProperty(AiProps.RESERVE_MANA_FOR_MAIN2_CHANCE);
-
// Mana reserved for spell synchronization
if (AiCardMemory.isRememberedCard(ai, sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL)) {
return true;
}
PhaseType curPhase = ai.getGame().getPhaseHandler().getPhase();
+ AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
+ int chanceToReserve = aic.getIntProperty(AiProps.RESERVE_MANA_FOR_MAIN2_CHANCE);
// For combat tricks, always obey mana reservation
if (curPhase == PhaseType.COMBAT_DECLARE_BLOCKERS || curPhase == PhaseType.CLEANUP) {
diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
index 1ba7c81e222..0bd597ae759 100644
--- a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
+++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java
@@ -33,7 +33,7 @@ public class CreatureEvaluator implements Function {
}
int power = getEffectivePower(c);
final int toughness = getEffectiveToughness(c);
-
+
// TODO replace with ReplacementEffect checks
if (c.hasKeyword("Prevent all combat damage that would be dealt by CARDNAME.")
|| c.hasKeyword("Prevent all damage that would be dealt by CARDNAME.")
@@ -49,7 +49,7 @@ public class CreatureEvaluator implements Function {
if (considerCMC) {
value += addValue(c.getCMC() * 5, "cmc");
}
-
+
// Evasion keywords
if (c.hasKeyword(Keyword.FLYING)) {
value += addValue(power * 10, "flying");
@@ -76,7 +76,7 @@ public class CreatureEvaluator implements Function {
value += addValue(power * 3, "block-restrict");
}
}
-
+
// Other good keywords
if (power > 0) {
if (c.hasKeyword(Keyword.DOUBLE_STRIKE)) {
@@ -105,7 +105,7 @@ public class CreatureEvaluator implements Function {
value += addValue(c.getKeywordMagnitude(Keyword.RAMPAGE), "rampage");
value += addValue(c.getKeywordMagnitude(Keyword.AFFLICT) * 5, "afflict");
}
-
+
value += addValue(c.getKeywordMagnitude(Keyword.BUSHIDO) * 16, "bushido");
value += addValue(c.getAmountOfKeyword(Keyword.FLANKING) * 15, "flanking");
value += addValue(c.getAmountOfKeyword(Keyword.EXALTED) * 15, "exalted");
@@ -127,7 +127,7 @@ public class CreatureEvaluator implements Function {
if (c.hasKeyword("CARDNAME can block creatures with shadow as though they didn't have shadow.")) {
value += addValue(3, "shadow-block");
}
-
+
// Protection
if (c.hasKeyword(Keyword.INDESTRUCTIBLE)) {
value += addValue(70, "darksteel");
@@ -161,11 +161,11 @@ public class CreatureEvaluator implements Function {
}/* else if (c.hasKeyword("CARDNAME can block only creatures with flying.")) {
value -= subValue(toughness * 5, "reverse-reach");
}//*/
-
+
if (c.hasSVar("DestroyWhenDamaged")) {
value -= subValue((toughness - 1) * 9, "dies-to-dmg");
}
-
+
if (c.hasKeyword("CARDNAME can't attack or block.")) {
value = addValue(50 + (c.getCMC() * 5), "useless"); // reset everything - useless
}
@@ -185,7 +185,7 @@ public class CreatureEvaluator implements Function {
} else if (c.hasKeyword(Keyword.ECHO) && c.cameUnderControlSinceLastUpkeep()) {
value -= subValue(10, "echo-unpaid");
}
-
+
if (c.hasStartOfKeyword("At the beginning of your upkeep, CARDNAME deals")) {
value -= subValue(20, "upkeep-dmg");
}
@@ -198,7 +198,7 @@ public class CreatureEvaluator implements Function {
if (c.getSVar("Targeting").equals("Dies")) {
value -= subValue(25, "dies");
}
-
+
for (final SpellAbility sa : c.getSpellAbilities()) {
if (sa.isAbility()) {
value += addValue(evaluateSpellAbility(sa), "sa: " + sa);
@@ -207,11 +207,11 @@ public class CreatureEvaluator implements Function {
if (!c.getManaAbilities().isEmpty()) {
value += addValue(10, "manadork");
}
-
+
if (c.isUntapped()) {
value += addValue(1, "untapped");
}
-
+
// paired creatures are more valuable because they grant a bonus to the other creature
if (c.isPaired()) {
value += addValue(14, "paired");
diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
index fee43f50e54..c94ce865d80 100644
--- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
+++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
@@ -330,7 +330,7 @@ public class PlayerControllerAi extends PlayerController {
@Override
public List exertAttackers(List attackers) {
- return AiAttackController.exertAttackers(attackers);
+ return AiAttackController.exertAttackers(attackers, brains.getAttackAggression());
}
@Override
@@ -797,7 +797,7 @@ public class PlayerControllerAi extends PlayerController {
return true;
} else {
Card rem = (Card) source.getFirstRemembered();
- if (!rem.isInZone(ZoneType.Battlefield)) {
+ if (!rem.isInPlay()) {
return true;
}
}
@@ -806,7 +806,7 @@ public class PlayerControllerAi extends PlayerController {
if (source.getRememberedCount() > 0) {
Card rem = (Card) source.getFirstRemembered();
// avoid pumping opponent creature
- if (!rem.isInZone(ZoneType.Battlefield) || rem.getController().isOpponentOf(source.getController())) {
+ if (!rem.isInPlay() || rem.getController().isOpponentOf(source.getController())) {
return true;
}
for (Card c : source.getController().getCreaturesInPlay()) {
diff --git a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java
index a49de701014..400db29d152 100644
--- a/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java
+++ b/forge-ai/src/main/java/forge/ai/SpellAbilityAi.java
@@ -73,7 +73,7 @@ public abstract class SpellAbilityAi {
if (sa.hasParam("AILogic")) {
final String logic = sa.getParam("AILogic");
final boolean alwaysOnDiscard = "AlwaysOnDiscard".equals(logic) && ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN, ai)
- && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize();
+ && !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize();
if (!checkAiLogic(ai, sa, logic)) {
return false;
}
@@ -162,21 +162,21 @@ public abstract class SpellAbilityAi {
*/
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
if (ComputerUtil.preventRunAwayActivations(sa)) {
- return false; // prevent infinite loop
+ return false; // prevent infinite loop
}
return MyRandom.getRandom().nextFloat() < .8f; // random success
}
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
+ // this evaluation order is currently intentional as it does more stuff that helps avoiding some crashes
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
return false;
}
// a mandatory SpellAbility with targeting but without candidates,
// does not need to go any deeper
- if (sa.usesTargeting() && mandatory && !sa.isTargetNumberValid()
- && !sa.getTargetRestrictions().hasCandidates(sa)) {
- return false;
+ if (sa.usesTargeting() && mandatory && sa.getTargetRestrictions().getNumCandidates(sa, true) == 0) {
+ return sa.isTargetNumberValid();
}
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
diff --git a/forge-ai/src/main/java/forge/ai/ability/AddTurnAi.java b/forge-ai/src/main/java/forge/ai/ability/AddTurnAi.java
index f8d1c1c9891..e67ddaee73e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AddTurnAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AddTurnAi.java
@@ -24,6 +24,8 @@ import org.apache.commons.lang3.StringUtils;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
+import forge.game.player.PlayerCollection;
+import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
/**
@@ -38,7 +40,8 @@ public class AddTurnAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
- final Player opp = ai.getWeakestOpponent();
+ PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
+ Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
if (sa.usesTargeting()) {
sa.resetTargets();
@@ -51,7 +54,7 @@ public class AddTurnAi extends SpellAbilityAi {
break;
}
}
- if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getHostCard(), sa) && sa.canTarget(opp)) {
+ if (!sa.getTargetRestrictions().isMinTargetsChosen(sa.getHostCard(), sa) && opp != null) {
sa.getTargets().add(opp);
} else {
return false;
diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
index e08152d3abd..363115939f3 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
@@ -233,7 +233,7 @@ public class AttachAi extends SpellAbilityAi {
boolean hasFloatMana = ai.getManaPool().totalMana() > 0;
boolean willDiscardNow = game.getPhaseHandler().is(PhaseType.END_OF_TURN, ai)
- && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize();
+ && !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize();
boolean willDieNow = combat != null && ComputerUtilCombat.lifeInSeriousDanger(ai, combat);
boolean willRespondToStack = canRespondToStack && MyRandom.percentTrue(chanceToRespondToStack);
boolean willCastEarly = MyRandom.percentTrue(chanceToCastEarly);
@@ -916,7 +916,7 @@ public class AttachAi extends SpellAbilityAi {
prefList = CardLists.filter(prefList, new Predicate() {
@Override
public boolean apply(final Card c) {
- return ComputerUtilCombat.canAttackNextTurn(c) && c.getNetPower() > 0;
+ return c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
}
});
}
@@ -1278,7 +1278,7 @@ public class AttachAi extends SpellAbilityAi {
if (!c.isCreature()) {
return true;
}
- return ComputerUtilCombat.canAttackNextTurn(c) && powerBonus + c.getNetPower() > 0;
+ return powerBonus + c.getNetPower() > 0 && ComputerUtilCombat.canAttackNextTurn(c);
}
});
}
@@ -1588,8 +1588,8 @@ public class AttachAi extends SpellAbilityAi {
if (evasive) {
return card.getNetCombatDamage() + powerBonus > 0
- && ComputerUtilCombat.canAttackNextTurn(card)
- && canBeBlocked;
+ && canBeBlocked
+ && ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.equals("Haste")) {
return card.hasSickness() && ph.isPlayerTurn(sa.getActivatingPlayer()) && !card.isTapped()
&& card.getNetCombatDamage() + powerBonus > 0
@@ -1609,8 +1609,8 @@ public class AttachAi extends SpellAbilityAi {
&& (ComputerUtilCombat.canAttackNextTurn(card) || CombatUtil.canBlock(card, true));
} else if (keyword.startsWith("Flanking")) {
return card.getNetCombatDamage() + powerBonus > 0
- && ComputerUtilCombat.canAttackNextTurn(card)
- && canBeBlocked;
+ && canBeBlocked
+ && ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.startsWith("Bushido")) {
return (canBeBlocked && ComputerUtilCombat.canAttackNextTurn(card))
|| CombatUtil.canBlock(card, true);
@@ -1652,9 +1652,9 @@ public class AttachAi extends SpellAbilityAi {
if (keyword.endsWith("CARDNAME can't attack.") || keyword.equals("Defender")
|| keyword.endsWith("CARDNAME can't attack or block.")) {
- return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
+ return card.getNetCombatDamage() >= 1 && ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.endsWith("CARDNAME attacks each turn if able.") || keyword.endsWith("CARDNAME attacks each combat if able.")) {
- return ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true) && !ai.getCreaturesInPlay().isEmpty();
+ return !ai.getCreaturesInPlay().isEmpty() && ComputerUtilCombat.canAttackNextTurn(card) && CombatUtil.canBlock(card, true);
} else if (keyword.endsWith("CARDNAME can't block.")) {
return CombatUtil.canBlock(card, true);
} else if (keyword.endsWith("CARDNAME's activated abilities can't be activated.")) {
@@ -1665,10 +1665,10 @@ public class AttachAi extends SpellAbilityAi {
}
return false;
} else if (keyword.endsWith("Prevent all combat damage that would be dealt by CARDNAME.")) {
- return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 1;
+ return card.getNetCombatDamage() >= 1 && ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.endsWith("Prevent all combat damage that would be dealt to and dealt by CARDNAME.")
|| keyword.endsWith("Prevent all damage that would be dealt to and dealt by CARDNAME.")) {
- return ComputerUtilCombat.canAttackNextTurn(card) && card.getNetCombatDamage() >= 2;
+ return card.getNetCombatDamage() >= 2 && ComputerUtilCombat.canAttackNextTurn(card);
} else if (keyword.endsWith("CARDNAME doesn't untap during your untap step.")) {
return !card.isUntapped();
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
index 2b621d1003a..9530308fe7f 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
@@ -1699,19 +1699,21 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (card.isToken()) {
return false;
}
-
if (card.isCreature() && ComputerUtilCard.isUselessCreature(decider, card)) {
return true;
- } else if (card.isEquipped()) {
+ }
+ if (card.isEquipped()) {
return false;
- } else if (card.isEnchanted()) {
+ }
+ if (card.isEnchanted()) {
for (Card enc : card.getEnchantedBy()) {
if (enc.getOwner().isOpponentOf(decider)) {
return true;
}
}
return false;
- } else if (card.hasCounters()) {
+ }
+ if (card.hasCounters()) {
if (card.isPlaneswalker()) {
int maxLoyaltyToConsider = 2;
int loyaltyDiff = 2;
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java
index f6680775cab..1755541f5ea 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java
@@ -392,15 +392,19 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) {
+ if (mandatory && !sa.isTargetNumberValid() && sa.canTarget(ai)) {
+ sa.resetTargets();
+ sa.getTargets().add(ai);
+ return true;
+ }
return false;
}
// get the one with the most handsize
- Player oppTarget = Collections.max(oppList,
- PlayerPredicates.compareByZoneSize(origin));
+ Player oppTarget = Collections.max(oppList, PlayerPredicates.compareByZoneSize(origin));
// set the target
- if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
+ if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty() || mandatory) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
@@ -434,7 +438,12 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
PlayerPredicates.isTargetableBy(sa)));
if (oppList.isEmpty()) {
- return false;
+ if (mandatory && !sa.isTargetNumberValid() && sa.canTarget(ai)) {
+ sa.resetTargets();
+ sa.getTargets().add(ai);
+ return true;
+ }
+ return sa.isTargetNumberValid();
}
// get the one with the most in graveyard
@@ -443,7 +452,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
// set the target
- if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
+ if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty() || mandatory) {
sa.resetTargets();
sa.getTargets().add(oppTarget);
} else {
diff --git a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java
index 36fee21c07d..f0cf10e2205 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java
@@ -69,6 +69,9 @@ public class CharmAi extends SpellAbilityAi {
if (timingRight) {
// Set minimum choices for triggers where chooseMultipleOptionsAi() returns null
chosenList = chooseOptionsAi(choices, ai, true, num, min, sa.hasParam("CanRepeatModes"));
+ if (chosenList.isEmpty() && min != 0) {
+ return false;
+ }
} else {
return false;
}
@@ -251,4 +254,23 @@ public class CharmAi extends SpellAbilityAi {
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable opponents, Map params) {
return Aggregates.random(opponents);
}
+
+ @Override
+ protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
+ // already done by chooseOrderOfSimultaneousStackEntry
+ if (sa.getChosenList() != null) {
+ return true;
+ }
+ return super.doTriggerAINoCost(aiPlayer, sa, mandatory);
+ }
+
+ @Override
+ public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) {
+ // choices were already targeted
+ if (ab.getRootAbility().getChosenList() != null) {
+ return true;
+ }
+ return super.chkDrawbackWithSubs(aiPlayer, ab);
+ }
+
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
index 2a6e465865a..31746c3e52c 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
@@ -4,7 +4,6 @@ import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
-import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class ChooseEvenOddAi extends SpellAbilityAi {
@@ -14,8 +13,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
if (!sa.hasParam("AILogic")) {
return false;
}
- TargetRestrictions tgt = sa.getTargetRestrictions();
- if (tgt != null) {
+ if (sa.usesTargeting()) {
sa.resetTargets();
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) {
@@ -34,4 +32,3 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
}
}
-
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
index 433b1af574f..7f181a24776 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java
@@ -295,7 +295,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
// if host would put into the battlefield attacking
if (combat != null && combat.isAttacking(host)) {
final Player defender = combat.getDefenderPlayerByAttacker(host);
- if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy)) {
+ if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy, true)) {
return counterSA;
}
return tokenSA;
@@ -306,7 +306,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
for (final Player opp : player.getOpponents()) {
if (CombatUtil.canAttack(copy, opp) &&
opp.canLoseLife() &&
- !ComputerUtilCard.canBeBlockedProfitably(opp, copy))
+ !ComputerUtilCard.canBeBlockedProfitably(opp, copy, true))
return counterSA;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java
index 06d132f5347..99b10c6e5ff 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java
@@ -6,7 +6,6 @@ import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
-import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class ChooseNumberAi extends SpellAbilityAi {
@@ -44,8 +43,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
return ownCreatureCount > oppMaxCreatureCount + 2 || ownCreatureCount < Math.min(oppMaxCreatureCount, maxChoiceLimit);
}
- TargetRestrictions tgt = sa.getTargetRestrictions();
- if (tgt != null) {
+ if (sa.usesTargeting()) {
sa.resetTargets();
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
index 7e2805a9add..78848367229 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
@@ -26,7 +26,6 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
-import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.Aggregates;
@@ -52,8 +51,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
}
}
- final TargetRestrictions tgt = sa.getTargetRestrictions();
- if (tgt != null) {
+ if (sa.usesTargeting()) {
sa.resetTargets();
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.canTarget(opp)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseTypeAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseTypeAi.java
index 07654174a59..dedf551ada4 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseTypeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseTypeAi.java
@@ -108,7 +108,7 @@ public class ChooseTypeAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
- boolean isCurse = sa.hasParam("IsCurse");
+ boolean isCurse = sa.isCurse();
if (sa.usesTargeting()) {
final List oppList = Lists.newArrayList(Iterables.filter(
diff --git a/forge-ai/src/main/java/forge/ai/ability/ClashAi.java b/forge-ai/src/main/java/forge/ai/ability/ClashAi.java
index b02be7b92ab..8541efdb2f7 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ClashAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ClashAi.java
@@ -92,7 +92,7 @@ public class ClashAi extends SpellAbilityAi {
if ("Creature".equals(valid)) {
// Springjack Knight
// TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support
- CardCollectionView aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
+ CardCollectionView aiCreats = ai.getCreaturesInPlay();
CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats);
diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
index 32aa71bbcc1..d4cfdca8c5e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
@@ -272,7 +272,16 @@ public class ControlGainAi extends SpellAbilityAi {
return true;
}
} else {
- if(sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
+ if (sa.hasParam("TargetingPlayer") || (!this.canPlayAI(ai, sa) && mandatory)) {
+ if (sa.getTargetRestrictions().canOnlyTgtOpponent()) {
+ List oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
+ if (oppList.isEmpty()) {
+ return false;
+ }
+ sa.getTargets().add(Aggregates.random(oppList));
+ return true;
+ }
+
List list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
if (list.isEmpty()) {
return false;
diff --git a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java
index f5a60501da9..4cc13b47baa 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java
@@ -128,19 +128,25 @@ public class CopyPermanentAi extends SpellAbilityAi {
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
final boolean canCopyLegendary = sa.hasParam("NonLegendary");
- // ////
- // Targeting
if (sa.usesTargeting()) {
sa.resetTargets();
CardCollection list = new CardCollection(CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa));
- list = CardLists.filter(list, Predicates.not(CardPredicates.isRemAIDeck()));
//Nothing to target
if (list.isEmpty()) {
return false;
}
-
+
+ CardCollection betterList = CardLists.filter(list, Predicates.not(CardPredicates.isRemAIDeck()));
+ if (betterList.isEmpty()) {
+ if (!mandatory) {
+ return false;
+ }
+ } else {
+ list = betterList;
+ }
+
// Saheeli Rai + Felidar Guardian combo support
if ("Saheeli Rai".equals(sourceName)) {
CardCollection felidarGuardian = CardLists.filter(list, CardPredicates.nameEquals("Felidar Guardian"));
@@ -154,7 +160,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
// target loop
while (sa.canAddMoreTarget()) {
if (list.isEmpty()) {
- if (!sa.isTargetNumberValid() || (sa.getTargets().size() == 0)) {
+ if (!sa.isTargetNumberValid() || sa.getTargets().size() == 0) {
sa.resetTargets();
return false;
} else {
diff --git a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java
index c24df2a5c39..000c52bc32c 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java
@@ -16,7 +16,6 @@ import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
-import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class CopySpellAbilityAi extends SpellAbilityAi {
@@ -58,8 +57,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
}
}
- final TargetRestrictions tgt = sa.getTargetRestrictions();
- if (tgt != null) {
+ if (sa.usesTargeting()) {
// Filter AI-specific targets if provided
if ("OnlyOwned".equals(sa.getParam("AITgts"))) {
if (!top.getActivatingPlayer().equals(aiPlayer)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java
index b93c01d40a3..7b5beebb67b 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java
@@ -27,7 +27,6 @@ import forge.game.cost.CostSacrifice;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
-import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
@@ -63,8 +62,7 @@ public class CounterAi extends SpellAbilityAi {
}
}
- final TargetRestrictions tgt = sa.getTargetRestrictions();
- if (tgt != null) {
+ if (sa.usesTargeting()) {
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
@@ -246,10 +244,9 @@ public class CounterAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
- final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = ai.getGame();
- if (tgt != null) {
+ if (sa.usesTargeting()) {
if (game.getStack().isEmpty()) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java
index a43320c7922..6c8699fbf87 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java
@@ -83,7 +83,7 @@ public class CountersMoveAi extends SpellAbilityAi {
}
// something you can't block, try to reduce its attack
- if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy)) {
+ if (!ComputerUtilCard.canBeBlockedProfitably(ai, cpy, false)) {
return true;
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
index 18f216f31b1..7de981a9c22 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java
@@ -442,7 +442,7 @@ public class CountersPutAi extends CountersAi {
}
}
- if ("AlwaysAtOppEOT".equals(logic)) {
+ if ("AtOppEOT".equals(logic)) {
if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai)) {
return true;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java
index c21c297fe62..05d98d165f7 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java
@@ -76,8 +76,8 @@ public abstract class DamageAiBase extends SpellAbilityAi {
Card hostcard = sa.getHostCard();
for (Trigger trig : hostcard.getTriggers()) {
if (trig.getMode() == TriggerType.DamageDone) {
- if (("Opponent".equals(trig.getParam("ValidTarget")))
- && (!"True".equals(trig.getParam("CombatDamage")))) {
+ if ("Opponent".equals(trig.getParam("ValidTarget"))
+ && !"True".equals(trig.getParam("CombatDamage"))) {
return true;
}
}
@@ -119,7 +119,7 @@ public abstract class DamageAiBase extends SpellAbilityAi {
PhaseHandler phase = game.getPhaseHandler();
// If this is a spell, cast it instead of discarding
if ((phase.is(PhaseType.END_OF_TURN) || phase.is(PhaseType.MAIN2))
- && phase.isPlayerTurn(comp) && (hand.size() > comp.getMaxHandSize())) {
+ && phase.isPlayerTurn(comp) && hand.size() > comp.getMaxHandSize()) {
return true;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
index 580509510e0..7e310dec65e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
@@ -448,7 +448,8 @@ public class DamageDealAi extends DamageAiBase {
// We can hurt a planeswalker, so rank the one which is the best target
if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) {
- return ComputerUtilCard.getBestPlaneswalkerToDamage(hPlay);
+ Card pw = ComputerUtilCard.getBestPlaneswalkerToDamage(hPlay);
+ return pw == null && mandatory ? hPlay.get(0) : pw;
}
return null;
@@ -566,7 +567,7 @@ public class DamageDealAi extends DamageAiBase {
TargetChoices tcs = sa.getTargets();
// Do not use if would kill self
- if (("SelfDamage".equals(logic)) && (ai.getLife() <= Integer.parseInt(source.getSVar("SelfDamageAmount")))) {
+ if ("SelfDamage".equals(logic) && ai.getLife() <= Integer.parseInt(source.getSVar("SelfDamageAmount"))) {
return false;
}
@@ -713,7 +714,7 @@ public class DamageDealAi extends DamageAiBase {
}
}
- if (freePing && sa.canTarget(enemy) && (!avoidTargetP(ai, sa))) {
+ if (freePing && sa.canTarget(enemy) && !avoidTargetP(ai, sa)) {
tcs.add(enemy);
if (divided) {
sa.addDividedAllocation(enemy, dmg);
diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageEachAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageEachAi.java
index 2471f3c52b0..59209d27f67 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageEachAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageEachAi.java
@@ -21,13 +21,12 @@ public class DamageEachAi extends DamageAiBase {
Player weakestOpp = targetableOpps.min(PlayerPredicates.compareByLife());
if (sa.usesTargeting() && weakestOpp != null) {
+ if ("MadSarkhanUltimate".equals(logic) && !SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp)) {
+ return false;
+ }
sa.resetTargets();
sa.getTargets().add(weakestOpp);
}
-
- if ("MadSarkhanUltimate".equals(logic)) {
- return SpecialCardAi.SarkhanTheMad.considerUltimate(ai, sa, weakestOpp);
- }
final String damage = sa.getParam("NumDmg");
final int iDmg = AbilityUtils.calculateAmount(sa.getHostCard(), damage, sa);
diff --git a/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java b/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java
index 6acb3b2cc2a..832c1313dce 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java
@@ -91,8 +91,7 @@ public class DebuffAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if (!sa.usesTargeting()) {
- // TODO - copied from AF_Pump.pumpDrawbackAI() - what should be
- // here?
+ // TODO - copied from AF_Pump.pumpDrawbackAI() - what should be here?
} else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
index f5b182846ce..7113db74e6f 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java
@@ -371,7 +371,7 @@ public class DestroyAi extends SpellAbilityAi {
break;
}
} else {
- break;
+ return true;
}
} else {
Card c = ComputerUtilCard.getBestAI(preferred);
@@ -380,7 +380,7 @@ public class DestroyAi extends SpellAbilityAi {
}
}
- while (sa.canAddMoreTarget()) {
+ while (!sa.isMinTargetChosen()) {
if (list.isEmpty()) {
break;
} else {
diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
index 17701bc2d77..1daa3a9991e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
@@ -148,13 +148,13 @@ public class DestroyAllAi extends SpellAbilityAi {
for (Card att : opponent.getCreaturesInPlay()) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ai);
- containsAttacker = containsAttacker | opplist.contains(att);
+ containsAttacker = containsAttacker || opplist.contains(att);
}
}
if (!containsAttacker) {
return false;
}
- AiBlockController block = new AiBlockController(ai);
+ AiBlockController block = new AiBlockController(ai, false);
block.assignBlockersForCombat(combat);
if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/DigAi.java b/forge-ai/src/main/java/forge/ai/ability/DigAi.java
index e16d8ce48df..9ae979ce786 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DigAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DigAi.java
@@ -47,7 +47,7 @@ public class DigAi extends SpellAbilityAi {
if (sa.usesTargeting()) {
sa.resetTargets();
- if (!opp.canBeTargetedBy(sa)) {
+ if (!sa.canTarget(opp)) {
return false;
}
sa.getTargets().add(opp);
@@ -61,7 +61,7 @@ public class DigAi extends SpellAbilityAi {
if ("Never".equals(sa.getParam("AILogic"))) {
return false;
- } else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
+ } else if ("AtOppEOT".equals(sa.getParam("AILogic"))) {
if (!(game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN))) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java b/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java
index 76cd8df5362..08a6c192914 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java
@@ -40,7 +40,7 @@ public class DigMultipleAi extends SpellAbilityAi {
if ("Never".equals(sa.getParam("AILogic"))) {
return false;
- } else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) {
+ } else if ("AtOppEOT".equals(sa.getParam("AILogic"))) {
if (!(game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN))) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
index 331acd2a61a..dc11d808114 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
@@ -130,8 +130,7 @@ public class DigUntilAi extends SpellAbilityAi {
if ("OathOfDruids".equals(logic)) {
final List creaturesInLibrary =
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
- final List creaturesInBattlefield =
- CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
+ final List creaturesInBattlefield = player.getCreaturesInPlay();
// if there are at least 3 creatures in library,
// or none in play with one in library, oath
return creaturesInLibrary.size() > 2
diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
index 48a3dcce9a0..9d7f339bf7e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
@@ -140,7 +140,7 @@ public class DrawAi extends SpellAbilityAi {
// LifeLessThan logic presupposes activation as soon as possible in an
// attempt to save the AI from dying
return true;
- } else if (logic.equals("AlwaysAtOppEOT")) {
+ } else if (logic.equals("AtOppEOT")) {
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai);
} else if (logic.equals("RespondToOwnActivation")) {
return !ai.getGame().getStack().isEmpty() && ai.getGame().getStack().peekAbility().getHostCard().equals(sa.getHostCard());
@@ -176,7 +176,7 @@ public class DrawAi extends SpellAbilityAi {
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
- return targetAI(ai, sa, false);
+ return targetAI(ai, sa, sa.isTrigger() && sa.getHostCard().isInPlay());
}
/**
diff --git a/forge-ai/src/main/java/forge/ai/ability/EncodeAi.java b/forge-ai/src/main/java/forge/ai/ability/EncodeAi.java
index 3f68bd73ed8..6627b3847c0 100644
--- a/forge-ai/src/main/java/forge/ai/ability/EncodeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/EncodeAi.java
@@ -61,7 +61,6 @@ public final class EncodeAi extends SpellAbilityAi {
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
return true;
}
-
/*
* (non-Javadoc)
diff --git a/forge-ai/src/main/java/forge/ai/ability/FightAi.java b/forge-ai/src/main/java/forge/ai/ability/FightAi.java
index 4b8771b2d2c..d5e36f5a8bc 100644
--- a/forge-ai/src/main/java/forge/ai/ability/FightAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/FightAi.java
@@ -66,7 +66,7 @@ public class FightAi extends SpellAbilityAi {
}
}
if (fighter1List.isEmpty()) {
- return true; // FIXME: shouldn't this return "false" if nothing found?
+ return false;
}
Card fighter1 = fighter1List.get(0);
for (Card humanCreature : humCreatures) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/FlipACoinAi.java b/forge-ai/src/main/java/forge/ai/ability/FlipACoinAi.java
index d41ac5a2aea..efa2a43fdb3 100644
--- a/forge-ai/src/main/java/forge/ai/ability/FlipACoinAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/FlipACoinAi.java
@@ -54,7 +54,7 @@ public class FlipACoinAi extends SpellAbilityAi {
return false;
}
}
- return true;
+ return sa.isTargetNumberValid();
}
@Override
diff --git a/forge-ai/src/main/java/forge/ai/ability/GameLossAi.java b/forge-ai/src/main/java/forge/ai/ability/GameLossAi.java
index 762db5c2189..7dafdfe4b64 100644
--- a/forge-ai/src/main/java/forge/ai/ability/GameLossAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/GameLossAi.java
@@ -17,11 +17,10 @@ public class GameLossAi extends SpellAbilityAi {
if (sa.usesTargeting()) {
sa.resetTargets();
sa.getTargets().add(opp);
+ return true;
}
- // In general, don't return true.
- // But this card wins the game, I can make an exception for that
- return true;
+ return false;
}
@Override
@@ -35,7 +34,7 @@ public class GameLossAi extends SpellAbilityAi {
loser = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
}
- if (!mandatory && loser.cantLose()) {
+ if (!mandatory && (loser == ai || loser.cantLose())) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/GoadAi.java b/forge-ai/src/main/java/forge/ai/ability/GoadAi.java
index b2d8b30b9ef..cf8c8de79fe 100644
--- a/forge-ai/src/main/java/forge/ai/ability/GoadAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/GoadAi.java
@@ -65,7 +65,7 @@ public class GoadAi extends SpellAbilityAi {
return false;
}
// select only creatures AI can block
- return ComputerUtilCard.canBeBlockedProfitably(ai, c);
+ return ComputerUtilCard.canBeBlockedProfitably(ai, c, false);
}
});
diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java
index 6ad44da50f6..71cdad4a7be 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java
@@ -1,8 +1,9 @@
package forge.ai.ability;
-import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
+import forge.game.player.PlayerCollection;
+import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.util.MyRandom;
@@ -18,14 +19,15 @@ public class LifeExchangeAi extends SpellAbilityAi {
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
- final int myLife = aiPlayer.getLife();
- Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
- final int hLife = opponent.getLife();
-
if (!aiPlayer.canGainLife()) {
return false;
}
+ final int myLife = aiPlayer.getLife();
+ final PlayerCollection targetableOpps = aiPlayer.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
+ final Player opponent = targetableOpps.max(PlayerPredicates.compareByLife());
+ final int hLife = opponent == null ? 0 : opponent.getLife();
+
// prevent run-away activations - first time will always return true
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
@@ -36,24 +38,23 @@ public class LifeExchangeAi extends SpellAbilityAi {
*/
if (sa.usesTargeting()) {
sa.resetTargets();
- if (opponent.canBeTargetedBy(sa)) {
+ if (opponent != null && opponent.canLoseLife()) {
// never target self, that would be silly for exchange
sa.getTargets().add(opponent);
- if (!opponent.canLoseLife()) {
- return false;
- }
+ } else {
+ return false;
}
}
// if life is in danger, always activate
- if ((myLife < 5) && (hLife > myLife)) {
+ if (myLife < 5 && hLife > myLife) {
return true;
}
// cost includes sacrifice probably, so make sure it's worth it
chance &= (hLife > (myLife + 8));
- return ((MyRandom.getRandom().nextFloat() < .6667) && chance);
+ return MyRandom.getRandom().nextFloat() < .6667 && chance;
}
/**
@@ -70,13 +71,16 @@ public class LifeExchangeAi extends SpellAbilityAi {
* @return a boolean.
*/
@Override
- protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
- final boolean mandatory) {
- Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
+ protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
+ PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
+ Player opp = targetableOpps.max(PlayerPredicates.compareByLife());
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
sa.getTargets().add(opp);
+ if (sa.canAddMoreTarget()) {
+ sa.getTargets().add(ai);
+ }
} else {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java
index f10c29d4ccd..f5f57169981 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java
@@ -149,8 +149,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
* @return a boolean.
*/
@Override
- protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa,
- final boolean mandatory) {
+ protected boolean doTriggerAINoCost(final Player ai, final SpellAbility sa, final boolean mandatory) {
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) {
sa.resetTargets();
diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java
index 29b295018a8..41e62d105fb 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeLoseAi.java
@@ -38,7 +38,7 @@ public class LifeLoseAi extends SpellAbilityAi {
SpellAbility root = sa.getRootAbility();
if (root.getXManaCostPaid() != null) {
amount = root.getXManaCostPaid();
- } else {
+ } else if (root.getPayCosts() != null && root.getPayCosts().hasXInAnyCostPart()) {
// Set PayX here to maximum value.
final int xPay = ComputerUtilCost.getMaxXValue(sa, ai);
root.setXManaCostPaid(xPay);
diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java
index ef07915ded9..fbe6b154cfd 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java
@@ -1,15 +1,21 @@
package forge.ai.ability;
+import com.google.common.collect.Iterables;
+
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
+import forge.game.card.CardPredicates;
import forge.game.card.CounterEnumType;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
+import forge.game.player.PlayerCollection;
+import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
+import forge.game.zone.ZoneType;
import forge.util.MyRandom;
public class LifeSetAi extends SpellAbilityAi {
@@ -17,14 +23,11 @@ public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final int myLife = ai.getLife();
- final Player opponent = ai.getStrongestOpponent();
- final int hlife = opponent.getLife();
+ final PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
+ final Player opponent = targetableOpps.max(PlayerPredicates.compareByLife());
+ final int hlife = opponent == null ? 0 : opponent.getLife();
final String amountStr = sa.getParam("LifeAmount");
- if (!ai.canGainLife()) {
- return false;
- }
-
// Don't use setLife before main 2 if possible
if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
&& !sa.hasParam("ActivationPhases")) {
@@ -55,20 +58,20 @@ public class LifeSetAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
- sa.getTargets().add(opponent);
// if we can only target the human, and the Human's life
// would go up, don't play it.
// possibly add a combo here for Magister Sphinx and
// Higedetsu's (sp?) Second Rite
- if ((amount > hlife) || !opponent.canLoseLife()) {
+ if (opponent == null || amount > hlife || !opponent.canLoseLife()) {
return false;
}
+ sa.getTargets().add(opponent);
} else {
- if ((amount > myLife) && (myLife <= 10)) {
+ if (amount > myLife && myLife <= 10 && ai.canGainLife()) {
sa.getTargets().add(ai);
} else if (hlife > amount) {
sa.getTargets().add(opponent);
- } else if (amount > myLife) {
+ } else if (amount > myLife && ai.canGainLife()) {
sa.getTargets().add(ai);
} else {
return false;
@@ -90,18 +93,19 @@ public class LifeSetAi extends SpellAbilityAi {
}
// if life is in danger, always activate
- if ((myLife < 3) && (amount > myLife)) {
+ if (myLife < 3 && amount > myLife && ai.canGainLife()) {
return true;
}
- return ((MyRandom.getRandom().nextFloat() < .6667) && chance);
+ return MyRandom.getRandom().nextFloat() < .6667 && chance;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final int myLife = ai.getLife();
- final Player opponent = ai.getStrongestOpponent();
- final int hlife = opponent.getLife();
+ final PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
+ final Player opponent = targetableOpps.max(PlayerPredicates.compareByLife());
+ final int hlife = opponent == null ? 0 : opponent.getLife();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
@@ -120,11 +124,11 @@ public class LifeSetAi extends SpellAbilityAi {
// special cases when amount can't be calculated without targeting first
if (amount == 0 && "TargetedPlayer$StartingLife/HalfDown".equals(source.getSVar(amountStr))) {
// e.g. Torgaar, Famine Incarnate
- return doHalfStartingLifeLogic(ai, opponent, sa);
+ return doHalfStartingLifeLogic(ai, opponent, sa) || mandatory;
}
if (sourceName.equals("Eternity Vessel")
- && (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterEnumType.CHARGE) == 0))) {
+ && (Iterables.any(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Vampire Hexmage")) || (source.getCounters(CounterEnumType.CHARGE) == 0))) {
return false;
}
@@ -134,13 +138,16 @@ public class LifeSetAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
+ if (opponent == null) {
+ return false;
+ }
sa.getTargets().add(opponent);
} else {
if (amount > myLife && myLife <= 10) {
sa.getTargets().add(ai);
} else if (hlife > amount) {
sa.getTargets().add(opponent);
- } else if (amount > myLife) {
+ } else if (amount > myLife || mandatory) {
sa.getTargets().add(ai);
} else {
return false;
@@ -153,9 +160,9 @@ public class LifeSetAi extends SpellAbilityAi {
private boolean doHalfStartingLifeLogic(Player ai, Player opponent, SpellAbility sa) {
int aiAmount = ai.getStartingLife() / 2;
- int oppAmount = opponent.getStartingLife() / 2;
+ int oppAmount = opponent == null ? 0 : opponent.getStartingLife() / 2;
int aiLife = ai.getLife();
- int oppLife = opponent.getLife();
+ int oppLife = opponent == null ? 0 : opponent.getLife();
sa.resetTargets();
diff --git a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
index c4abbd58167..305cece5cdd 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java
@@ -14,7 +14,9 @@ import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.card.ColorSet;
import forge.card.MagicColor;
+import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCost;
+import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -25,6 +27,8 @@ import forge.game.card.CounterType;
import forge.game.cost.CostPart;
import forge.game.cost.CostRemoveCounter;
import forge.game.keyword.Keyword;
+import forge.game.mana.Mana;
+import forge.game.mana.ManaPool;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
@@ -61,6 +65,9 @@ public class ManaEffectAi extends SpellAbilityAi {
*/
@Override
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph) {
+ if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai && canRampPool(ai, sa.getHostCard())) {
+ return true;
+ }
if (!ph.is(PhaseType.MAIN2) || !ComputerUtil.activateForCost(sa, ai)) {
return false;
}
@@ -79,6 +86,8 @@ public class ManaEffectAi extends SpellAbilityAi {
protected boolean checkPhaseRestrictions(Player ai, SpellAbility sa, PhaseHandler ph, String logic) {
if (logic.startsWith("ManaRitual")) {
return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai);
+ } else if ("AtOppEOT".equals(logic)) {
+ return !ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn) && ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
}
return super.checkPhaseRestrictions(ai, sa, ph, logic);
}
@@ -95,9 +104,14 @@ public class ManaEffectAi extends SpellAbilityAi {
return true; // handled elsewhere, does not meet the standard requirements
}
+ PhaseHandler ph = ai.getGame().getPhaseHandler();
+ boolean moreManaNextTurn = false;
+ if (ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai && canRampPool(ai, sa.getHostCard())) {
+ moreManaNextTurn = true;
+ }
+
return sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
- && sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa);
- // return super.checkApiLogic(ai, sa);
+ && sa.getSubAbility() == null && (ComputerUtil.playImmediately(ai, sa) || moreManaNextTurn);
}
/**
@@ -244,4 +258,18 @@ public class ManaEffectAi extends SpellAbilityAi {
// TODO: this will probably still waste the card from time to time. Somehow improve detection of castable material.
return castableSpells.size() > 0;
}
+
+ private boolean canRampPool(Player ai, Card source) {
+ ManaPool mp = ai.getManaPool();
+ Mana test = null;
+ if (mp.isEmpty()) {
+ test = new Mana((byte) ManaAtom.COLORLESS, source, null);
+ mp.addMana(test, false);
+ }
+ boolean lose = mp.willManaBeLostAtEndOfPhase();
+ if (test != null) {
+ mp.removeMana(test, false);
+ }
+ return !lose;
+ }
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/MillAi.java b/forge-ai/src/main/java/forge/ai/ability/MillAi.java
index f6802577a72..ccaf7fcef14 100644
--- a/forge-ai/src/main/java/forge/ai/ability/MillAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/MillAi.java
@@ -146,13 +146,13 @@ public class MillAi extends SpellAbilityAi {
// can't target opponent?
if (list.isEmpty()) {
- if (mandatory && sa.canTarget(ai)) {
+ if (mandatory && !sa.isTargetNumberValid() && sa.canTarget(ai)) {
sa.getTargets().add(ai);
return true;
}
// TODO Obscure case when you know what your top card is so you might?
// want to mill yourself here
- return false;
+ return sa.isTargetNumberValid();
}
// select Player which would cause the most damage
diff --git a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java
index 4e6a51dcfde..288b8f79163 100644
--- a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java
@@ -5,7 +5,6 @@ import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
import forge.ai.AiCardMemory;
import forge.ai.ComputerUtilCard;
@@ -15,14 +14,12 @@ import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardLists;
-import forge.game.card.CardPredicates;
import forge.game.combat.Combat;
import forge.game.combat.CombatUtil;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
-import forge.game.zone.ZoneType;
public class MustBlockAi extends SpellAbilityAi {
@@ -76,7 +73,7 @@ public class MustBlockAi extends SpellAbilityAi {
return false;
}
- Card attacker = null;
+ Card attacker = source;
if (sa.hasParam("DefinedAttacker")) {
final List cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
if (cards.isEmpty()) {
@@ -86,18 +83,12 @@ public class MustBlockAi extends SpellAbilityAi {
attacker = cards.get(0);
}
- if (attacker == null) {
- attacker = source;
- }
-
- final Card definedAttacker = attacker;
-
boolean chance = false;
if (sa.usesTargeting()) {
- final List list = determineGoodBlockers(definedAttacker, ai, ai.getWeakestOpponent(), sa, true,true);
+ final List list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true);
if (list.isEmpty()) {
- return false;
+ return sa.isTargetNumberValid();
}
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
if (blocker == null) {
@@ -160,8 +151,7 @@ public class MustBlockAi extends SpellAbilityAi {
private List determineGoodBlockers(final Card attacker, final Player ai, Player defender, SpellAbility sa,
final boolean onlyLethal, final boolean testTapped) {
- List list = Lists.newArrayList();
- list = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
+ List list = defender.getCreaturesInPlay();
if (sa.usesTargeting()) {
list = CardLists.getTargetableCards(list, sa);
@@ -187,7 +177,7 @@ public class MustBlockAi extends SpellAbilityAi {
List better = determineBlockerFromList(attacker, ai, options, sa, false, false);
if (!better.isEmpty()) {
- return Iterables.getFirst(options, null);
+ return Iterables.getFirst(better, null);
}
return Iterables.getFirst(options, null);
diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java
index 0e95d23db25..924c8e86784 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java
@@ -140,7 +140,7 @@ public class PermanentCreatureAi extends PermanentAi {
boolean hasAmbushAI = card.hasSVar("AmbushAI");
boolean defOnlyAmbushAI = hasAmbushAI && "BlockOnly".equals(card.getSVar("AmbushAI"));
boolean hasFloatMana = ai.getManaPool().totalMana() > 0;
- boolean willDiscardNow = isOwnEOT && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize();
+ boolean willDiscardNow = isOwnEOT && !ai.isUnlimitedHandSize() && ai.getCardsIn(ZoneType.Hand).size() > ai.getMaxHandSize();
boolean willDieNow = combat != null && ComputerUtilCombat.lifeInSeriousDanger(ai, combat);
boolean wantToCastInMain1 = ph.is(PhaseType.MAIN1, ai) && ComputerUtil.castPermanentInMain1(ai, sa);
boolean isCommander = card.isCommander();
diff --git a/forge-ai/src/main/java/forge/ai/ability/PhasesAi.java b/forge-ai/src/main/java/forge/ai/ability/PhasesAi.java
index 8973d02888a..a033ce0b613 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PhasesAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PhasesAi.java
@@ -63,7 +63,7 @@ public class PhasesAi extends SpellAbilityAi {
return true;
} else if (mandatory) {
// not enough preferred targets, but mandatory so keep going:
- return phasesUnpreferredTargeting(aiPlayer.getGame(), sa, mandatory);
+ return sa.isTargetNumberValid() || phasesUnpreferredTargeting(aiPlayer.getGame(), sa, mandatory);
}
return false;
diff --git a/forge-ai/src/main/java/forge/ai/ability/PoisonAi.java b/forge-ai/src/main/java/forge/ai/ability/PoisonAi.java
index b9547f2e875..d6a65513f4e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PoisonAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PoisonAi.java
@@ -25,8 +25,7 @@ public class PoisonAi extends SpellAbilityAi {
*/
@Override
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
- return !ph.getPhase().isBefore(PhaseType.MAIN2)
- || sa.hasParam("ActivationPhases");
+ return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases");
}
/*
diff --git a/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java b/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java
index fa362f97a0d..04f3e23379a 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java
@@ -31,11 +31,10 @@ public class PowerExchangeAi extends SpellAbilityAi {
List list =
CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
- // AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate() {
@Override
public boolean apply(final Card c) {
- return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa) && c.getController() != ai;
+ return c.canBeTargetedBy(sa) && c.getController() != ai;
}
});
CardLists.sortByPowerAsc(list);
diff --git a/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java b/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java
index 69765ce7717..6352447e814 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ProtectAllAi.java
@@ -26,7 +26,6 @@ public class ProtectAllAi extends SpellAbilityAi {
return false;
} // protectAllCanPlayAI()
-
@Override
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
return true;
diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java
index e240f51cd27..a95a44b904a 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java
@@ -23,7 +23,6 @@ import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
-import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class PumpAllAi extends PumpAiBase {
@@ -57,19 +56,20 @@ public class PumpAllAi extends PumpAiBase {
}
}
- final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getStrongestOpponent();
- if (tgt != null && sa.canTarget(opp) && sa.isCurse()) {
- sa.resetTargets();
- sa.getTargets().add(opp);
- return true;
- }
-
- if (tgt != null && sa.canTarget(ai) && !sa.isCurse()) {
- sa.resetTargets();
- sa.getTargets().add(ai);
- return true;
+ if (sa.usesTargeting()) {
+ if (sa.canTarget(opp) && sa.isCurse()) {
+ sa.resetTargets();
+ sa.getTargets().add(opp);
+ return true;
+ }
+
+ if (sa.canTarget(ai) && !sa.isCurse()) {
+ sa.resetTargets();
+ sa.getTargets().add(ai);
+ return true;
+ }
}
final int power = AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa);
diff --git a/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java b/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java
index 408d912f76b..b446d81c98c 100644
--- a/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java
@@ -53,7 +53,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
PlayerCollection targetableOpps = aiPlayer.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
final boolean canTgtAI = sa.canTarget(aiPlayer);
- final boolean canTgtHuman = opp != null && sa.canTarget(opp);
+ final boolean canTgtHuman = sa.canTarget(opp);
if (canTgtHuman && canTgtAI) {
// TODO: maybe some other consideration rather than random?
diff --git a/forge-ai/src/main/java/forge/ai/ability/RegenerateAi.java b/forge-ai/src/main/java/forge/ai/ability/RegenerateAi.java
index 5a1f1132f58..cfb749274fe 100644
--- a/forge-ai/src/main/java/forge/ai/ability/RegenerateAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/RegenerateAi.java
@@ -138,12 +138,11 @@ public class RegenerateAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
boolean chance = false;
- final TargetRestrictions tgt = sa.getTargetRestrictions();
- if (tgt == null) {
+ if (sa.usesTargeting()) {
+ chance = regenMandatoryTarget(ai, sa, mandatory);
+ } else {
// If there's no target on the trigger, just say yes.
chance = true;
- } else {
- chance = regenMandatoryTarget(ai, sa, mandatory);
}
return chance;
@@ -164,7 +163,7 @@ public class RegenerateAi extends SpellAbilityAi {
return false;
}
- if (!mandatory && (compTargetables.size() == 0)) {
+ if (!mandatory && compTargetables.size() == 0) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java
index 5a94167b7bf..0b6f2e3ddc8 100644
--- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java
@@ -15,6 +15,8 @@ import forge.game.card.CardPredicates;
import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
+import forge.game.player.PlayerCollection;
+import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
@@ -22,14 +24,14 @@ public class SacrificeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
- return sacrificeTgtAI(ai, sa);
+ return sacrificeTgtAI(ai, sa, false);
}
@Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) {
// AI should only activate this during Human's turn
- return sacrificeTgtAI(ai, sa);
+ return sacrificeTgtAI(ai, sa, false);
}
@Override
@@ -48,21 +50,24 @@ public class SacrificeAi extends SpellAbilityAi {
// Eventually, we can call the trigger of ETB abilities with not
// mandatory as part of the checks to cast something
- return sacrificeTgtAI(ai, sa) || mandatory;
+ return sacrificeTgtAI(ai, sa, mandatory) || mandatory;
}
- private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
+ private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa, boolean mandatory) {
final Card source = sa.getHostCard();
final boolean destroy = sa.hasParam("Destroy");
- Player opp = ai.getStrongestOpponent();
-
if (sa.usesTargeting()) {
- sa.resetTargets();
- if (!opp.canBeTargetedBy(sa)) {
+ final PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
+ if (targetableOpps.isEmpty()) {
return false;
}
+ final Player opp = targetableOpps.max(PlayerPredicates.compareByLife());
+ sa.resetTargets();
sa.getTargets().add(opp);
+ if (mandatory) {
+ return true;
+ }
final String valid = sa.getParam("SacValid");
String num = sa.getParamOrDefault("Amount" , "1");
final int amount = AbilityUtils.calculateAmount(source, num, sa);
@@ -79,7 +84,7 @@ public class SacrificeAi extends SpellAbilityAi {
for (Card c : list) {
if (c.hasSVar("SacMe") && Integer.parseInt(c.getSVar("SacMe")) > 3) {
- return false;
+ return false;
}
}
if (!destroy) {
@@ -131,7 +136,7 @@ public class SacrificeAi extends SpellAbilityAi {
List humanList = null;
try {
- humanList = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa);
+ humanList = CardLists.getValidCards(ai.getStrongestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), sa.getActivatingPlayer(), source, sa);
} catch (NullPointerException e) {
return false;
} finally {
diff --git a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
index 91b74d2566f..8d38bd31b4e 100644
--- a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
@@ -33,6 +33,7 @@ public class SetStateAi extends SpellAbilityAi {
final String mode = sa.getParam("Mode");
// turning face is most likely okay
+ // TODO only do this at beneficial moment (e.g. surprise during combat or morph trigger), might want to reserve mana to protect them from easy removal
if ("TurnFace".equals(mode)) {
return true;
}
@@ -208,8 +209,8 @@ public class SetStateAi extends SpellAbilityAi {
// if an opponent can't block it, no need to transform (back)
for (Player opp : ai.getOpponents()) {
- boolean attackCard = !ComputerUtilCard.canBeBlockedProfitably(opp, original);
- boolean attackTransformed = !ComputerUtilCard.canBeBlockedProfitably(opp, copy);
+ boolean attackCard = !ComputerUtilCard.canBeBlockedProfitably(opp, original, true);
+ boolean attackTransformed = !ComputerUtilCard.canBeBlockedProfitably(opp, copy, true);
// both forms can attack, try to use the one with better value
if (attackCard && attackTransformed) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAllAi.java b/forge-ai/src/main/java/forge/ai/ability/TapAllAi.java
index dd89e45ab75..bfa6105413f 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TapAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TapAllAi.java
@@ -16,6 +16,8 @@ import forge.game.card.CardPredicates;
import forge.game.combat.CombatUtil;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
+import forge.game.player.PlayerCollection;
+import forge.game.player.PlayerPredicates;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom;
@@ -27,7 +29,7 @@ public class TapAllAi extends SpellAbilityAi {
// or during upkeep/begin combat?
final Card source = sa.getHostCard();
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = ai.getStrongestOpponent();
final Game game = ai.getGame();
if (game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN)) {
@@ -101,10 +103,14 @@ public class TapAllAi extends SpellAbilityAi {
CardCollectionView validTappables = getTapAllTargets(valid, source, sa);
if (sa.usesTargeting()) {
+ final PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
+ Player target = targetableOpps.max(PlayerPredicates.compareByLife());
+ if (target == null && mandatory) {
+ target = ai;
+ }
sa.resetTargets();
- Player opp = ai.getStrongestOpponent();
- sa.getTargets().add(opp);
- validTappables = opp.getCardsIn(ZoneType.Battlefield);
+ sa.getTargets().add(target);
+ validTappables = target.getCardsIn(ZoneType.Battlefield);
}
if (mandatory) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
index 69ce9ce0117..9bd2f20db2c 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java
@@ -33,6 +33,8 @@ import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
+import forge.game.player.PlayerCollection;
+import forge.game.player.PlayerPredicates;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
@@ -260,7 +262,12 @@ public class TokenAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
- sa.getTargets().add(ai.getWeakestOpponent());
+ PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
+ if (mandatory && targetableOpps.isEmpty()) {
+ return false;
+ }
+ Player opp = targetableOpps.min(PlayerPredicates.compareByLife());
+ sa.getTargets().add(opp);
} else {
sa.getTargets().add(ai);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/TwoPilesAi.java b/forge-ai/src/main/java/forge/ai/ability/TwoPilesAi.java
index 9fafd25f28d..1f1e61fe286 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TwoPilesAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TwoPilesAi.java
@@ -10,7 +10,6 @@ import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
-import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
import forge.util.collect.FCollection;
@@ -28,8 +27,7 @@ public class TwoPilesAi extends SpellAbilityAi {
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
- final TargetRestrictions tgt = sa.getTargetRestrictions();
- if (tgt != null) {
+ if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
diff --git a/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java b/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java
index 2bd45aa83ca..5a14e5962a0 100644
--- a/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java
@@ -12,7 +12,6 @@ import forge.game.card.Card;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
-import forge.game.spellability.TargetRestrictions;
import forge.util.MyRandom;
public class UnattachAllAi extends SpellAbilityAi {
@@ -48,7 +47,6 @@ public class UnattachAllAi extends SpellAbilityAi {
return chance;
}
-
/* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/
@@ -57,8 +55,7 @@ public class UnattachAllAi extends SpellAbilityAi {
final Card card = sa.getHostCard();
// Check if there are any valid targets
List targets = new ArrayList<>();
- final TargetRestrictions tgt = sa.getTargetRestrictions();
- if (tgt == null) {
+ if (!sa.usesTargeting()) {
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java
index cc038b3c7a4..7c4dc3329af 100644
--- a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java
@@ -6,7 +6,6 @@ import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
-import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
@@ -70,7 +69,6 @@ public class UntapAi extends SpellAbilityAi {
} else {
return untapPrefTargeting(ai, sa, false);
}
-
}
@Override
@@ -125,11 +123,11 @@ public class UntapAi extends SpellAbilityAi {
private static boolean untapPrefTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Card source = sa.getHostCard();
- Player targetController = ai;
-
+ final PlayerCollection targetController = new PlayerCollection();
if (sa.isCurse()) {
- // TODO search through all opponents, may need to check if different controllers allowed
- targetController = AiAttackController.choosePreferredDefenderPlayer(ai);
+ targetController.addAll(ai.getOpponents());
+ } else {
+ targetController.add(ai);
}
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
index 53df565bc54..e537b1754ca 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
@@ -8,8 +8,11 @@ import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
import forge.LobbyPlayer;
+import forge.ai.AIOption;
import forge.ai.LobbyPlayerAi;
import forge.game.Game;
import forge.game.GameEntity;
@@ -54,19 +57,19 @@ public class GameCopier {
public GameCopier(Game origGame) {
this.origGame = origGame;
}
-
+
public Game getOriginalGame() {
return origGame;
}
-
+
public Game getCopiedGame() {
return gameObjectMap.getGame();
}
-
+
public Game makeCopy() {
- return makeCopy(null);
+ return makeCopy(null, null);
}
- public Game makeCopy(PhaseType advanceToPhase) {
+ public Game makeCopy(PhaseType advanceToPhase, Player aiPlayer) {
List origPlayers = origGame.getMatch().getPlayers();
List newPlayers = new ArrayList<>();
for (RegisteredPlayer p : origPlayers) {
@@ -82,8 +85,7 @@ public class GameCopier {
newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn());
for (int j = 0; j < origPlayer.getSpellsCastThisTurn(); j++)
newPlayer.addSpellCastThisTurn();
- for (int j = 0; j < origPlayer.getLandsPlayedThisTurn(); j++)
- newPlayer.addLandPlayedThisTurn();
+ newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn());
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
@@ -99,7 +101,7 @@ public class GameCopier {
for (Player p : newGame.getPlayers()) {
((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(false);
}
-
+
copyGameState(newGame);
for (Player p : newGame.getPlayers()) {
@@ -138,20 +140,27 @@ public class GameCopier {
effect.removeMapped(gameObjectMap);
}
+ if (origPhaseHandler.getCombat() != null) {
+ newGame.getPhaseHandler().setCombat(new Combat(origPhaseHandler.getCombat(), gameObjectMap));
+ }
+
newGame.getAction().checkStateEffects(true); //ensure state based effects and triggers are updated
newGame.getTriggerHandler().resetActiveTriggers();
if (GameSimulator.COPY_STACK)
copyStack(origGame, newGame, gameObjectMap);
- if (origPhaseHandler.getCombat() != null) {
- newGame.getPhaseHandler().setCombat(new Combat(origPhaseHandler.getCombat(), gameObjectMap));
- }
+ // TODO update thisTurnCast
if (advanceToPhase != null) {
- newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase);
+ newGame.getPhaseHandler().devAdvanceToPhase(advanceToPhase, new Runnable() {
+ @Override
+ public void run() {
+ GameSimulator.resolveStack(newGame, aiPlayer.getWeakestOpponent());
+ }
+ });
}
-
+
return newGame;
}
@@ -185,7 +194,8 @@ public class GameCopier {
RegisteredPlayer clone = new RegisteredPlayer(p.getDeck());
LobbyPlayer lp = p.getPlayer();
if (!(lp instanceof LobbyPlayerAi)) {
- lp = new LobbyPlayerAi(p.getPlayer().getName(), null);
+ // TODO should probably also override them if they're normal AI
+ lp = new LobbyPlayerAi(p.getPlayer().getName(), Sets.newHashSet(AIOption.USE_SIMULATION));
}
clone.setPlayer(lp);
return clone;
@@ -197,6 +207,7 @@ public class GameCopier {
for (Card card : origGame.getCardsIn(zone)) {
addCard(newGame, zone, card);
}
+ // TODO CardsAddedThisTurn is now messed up
}
gameObjectMap = new CopiedGameObjectMap(newGame);
@@ -271,14 +282,17 @@ public class GameCopier {
cardMap.put(c, newCard);
Player zoneOwner = owner;
+ // everything the CreatureEvaluator checks must be set here
if (zone == ZoneType.Battlefield) {
// TODO: Controllers' list with timestamps should be copied.
zoneOwner = playerMap.get(c.getController());
newCard.setController(zoneOwner, 0);
+ newCard.setCameUnderControlSinceLastUpkeep(c.cameUnderControlSinceLastUpkeep());
+
newCard.setPTTable(c.getSetPTTable());
newCard.setPTCharacterDefiningTable(c.getSetPTCharacterDefiningTable());
-
+
newCard.setPTBoost(c.getPTBoostTable());
newCard.setDamage(c.getDamage());
@@ -290,9 +304,11 @@ public class GameCopier {
newCard.setChangedCardKeywords(c.getChangedCardKeywords());
newCard.setChangedCardNames(c.getChangedCardNames());
- // TODO: Is this correct? Does it not duplicate keywords from enchantments and such?
- //for (KeywordInterface kw : c.getHiddenExtrinsicKeywords())
- // newCard.addHiddenExtrinsicKeyword(kw);
+ for (Table.Cell> kw : c.getHiddenExtrinsicKeywordsTable().cellSet()) {
+ newCard.addHiddenExtrinsicKeywords(kw.getRowKey(), kw.getColumnKey(), kw.getValue());
+ }
+ newCard.updateKeywordsCache(newCard.getCurrentState());
+
if (c.isTapped()) {
newCard.setTapped(true);
}
@@ -358,7 +374,7 @@ public class GameCopier {
zoneOwner.getZone(zone).add(newCard);
}
}
-
+
private static SpellAbility findSAInCard(SpellAbility sa, Card c) {
String saDesc = sa.getDescription();
for (SpellAbility cardSa : c.getAllSpellAbilities()) {
@@ -386,7 +402,7 @@ public class GameCopier {
return find(o);
}
}
-
+
public GameObject find(GameObject o) {
GameObject result = cardMap.get(o);
if (result != null)
diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java
index 927b315bf00..7549c1f62cf 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java
@@ -31,11 +31,11 @@ public class GameSimulator {
public GameSimulator(SimulationController controller, Game origGame, Player origAiPlayer, PhaseType advanceToPhase) {
this.controller = controller;
copier = new GameCopier(origGame);
- simGame = copier.makeCopy(advanceToPhase);
+ simGame = copier.makeCopy(advanceToPhase, origAiPlayer);
aiPlayer = (Player) copier.find(origAiPlayer);
eval = new GameStateEvaluator();
-
+
origLines = new ArrayList<>();
debugLines = origLines;
@@ -74,6 +74,8 @@ public class GameSimulator {
eval.getScoreForGameState(origGame, origAiPlayer);
// Print debug info.
printDiff(origLines, simLines);
+ // make sure it gets printed
+ System.out.flush();
throw new RuntimeException("Game copy error. See diff output above for details.");
}
eval.setDebugging(false);
@@ -83,7 +85,7 @@ public class GameSimulator {
this.interceptor = interceptor;
((PlayerControllerAi) aiPlayer.getController()).getAi().getSimulationPicker().setInterceptor(interceptor);
}
-
+
private void printDiff(List lines1, List lines2) {
int i = 0;
int j = 0;
@@ -117,11 +119,11 @@ public class GameSimulator {
if (debugPrint) {
System.out.println(str);
}
- if (debugLines!=null) {
+ if (debugLines != null) {
debugLines.add(str);
}
}
-
+
private SpellAbility findSaInSimGame(SpellAbility sa) {
// is already an ability from sim game
if (sa.getHostCard().getGame().equals(this.simGame)) {
@@ -154,7 +156,7 @@ public class GameSimulator {
sa = findSaInSimGame(origSa);
if (sa == null) {
System.err.println("Simulation: SA not found! " + origSa);
- return new Score(Integer.MIN_VALUE, Integer.MIN_VALUE);
+ return new Score(Integer.MIN_VALUE);
}
debugPrint("Found SA " + sa + " on host card " + sa.getHostCard() + " with owner:"+ sa.getHostCard().getOwner());
@@ -186,14 +188,18 @@ public class GameSimulator {
final SpellAbility playingSa = sa;
simGame.copyLastState();
- ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, new Runnable() {
+ boolean success = ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, new Runnable() {
@Override
public void run() {
if (interceptor != null) {
+ interceptor.announceX(playingSa);
interceptor.chooseTargets(playingSa, GameSimulator.this);
}
}
});
+ if (!success) {
+ return new Score(Integer.MIN_VALUE);
+ }
}
// TODO: Support multiple opponents.
@@ -230,8 +236,10 @@ public class GameSimulator {
return score;
}
- private static void resolveStack(final Game game, final Player opponent) {
+ public static void resolveStack(final Game game, final Player opponent) {
// TODO: This needs to set an AI controller for all opponents, in case of multiplayer.
+ PlayerControllerAi sim = new PlayerControllerAi(game, opponent, opponent.getLobbyPlayer());
+ sim.setUseSimulation(true);
opponent.runWithController(new Runnable() {
@Override
public void run() {
@@ -259,9 +267,9 @@ public class GameSimulator {
// Continue until stack is empty.
}
}
- }, new PlayerControllerAi(game, opponent, opponent.getLobbyPlayer()));
+ }, sim);
}
-
+
public Game getSimulatedGameState() {
return simGame;
}
@@ -269,7 +277,7 @@ public class GameSimulator {
public Score getScoreForOrigGame() {
return origScore;
}
-
+
public GameCopier getGameCopier() {
return copier;
}
diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java
index e3dc9e8a64c..678ab769fce 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/GameStateEvaluator.java
@@ -15,16 +15,16 @@ public class GameStateEvaluator {
public void setDebugging(boolean debugging) {
this.debugging = debugging;
}
-
+
private static void debugPrint(String s) {
GameSimulator.debugPrint(s);
}
-
+
private static class CombatSimResult {
public GameCopier copier;
public Game gameCopy;
}
- private CombatSimResult simulateUpcomingCombatThisTurn(final Game evalGame) {
+ private CombatSimResult simulateUpcomingCombatThisTurn(final Game evalGame, final Player aiPlayer) {
PhaseType phase = evalGame.getPhaseHandler().getPhase();
if (phase.isAfter(PhaseType.COMBAT_DAMAGE) || evalGame.isGameOver()) {
return null;
@@ -38,7 +38,12 @@ public class GameStateEvaluator {
}
GameCopier copier = new GameCopier(evalGame);
Game gameCopy = copier.makeCopy();
- gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE);
+ gameCopy.getPhaseHandler().devAdvanceToPhase(PhaseType.COMBAT_DAMAGE, new Runnable() {
+ @Override
+ public void run() {
+ GameSimulator.resolveStack(gameCopy, aiPlayer.getWeakestOpponent());
+ }
+ });
CombatSimResult result = new CombatSimResult();
result.copier = copier;
result.gameCopy = gameCopy;
@@ -61,13 +66,13 @@ public class GameStateEvaluator {
return new Score(Integer.MIN_VALUE);
}
-
+
public Score getScoreForGameState(Game game, Player aiPlayer) {
if (game.isGameOver()) {
return getScoreForGameOver(game, aiPlayer);
}
-
- CombatSimResult result = simulateUpcomingCombatThisTurn(game);
+
+ CombatSimResult result = simulateUpcomingCombatThisTurn(game, aiPlayer);
if (result != null) {
Player aiPlayerCopy = (Player) result.copier.find(aiPlayer);
if (result.gameCopy.isGameOver()) {
@@ -81,6 +86,7 @@ public class GameStateEvaluator {
private Score getScoreForGameStateImpl(Game game, Player aiPlayer) {
int score = 0;
// TODO: more than 2 players
+ // TODO: try and reuse evaluateBoardPosition
int myCards = 0;
int theirCards = 0;
for (Card c : game.getCardsIn(ZoneType.Hand)) {
@@ -92,7 +98,7 @@ public class GameStateEvaluator {
}
debugPrint("My cards in hand: " + myCards);
debugPrint("Their cards in hand: " + theirCards);
- if (myCards > aiPlayer.getMaxHandSize()) {
+ if (!aiPlayer.isUnlimitedHandSize() && myCards > aiPlayer.getMaxHandSize()) {
// Count excess cards for less.
score += myCards - aiPlayer.getMaxHandSize();
myCards = aiPlayer.getMaxHandSize();
@@ -102,12 +108,10 @@ public class GameStateEvaluator {
score += 2 * aiPlayer.getLife();
int opponentIndex = 1;
int opponentLife = 0;
- for (Player opponent : game.getPlayers()) {
- if (opponent != aiPlayer) {
+ for (Player opponent : aiPlayer.getOpponents()) {
debugPrint(" Opponent " + opponentIndex + " life: -" + opponent.getLife());
opponentLife += opponent.getLife();
opponentIndex++;
- }
}
score -= 2* opponentLife / (game.getPlayers().size() - 1);
int summonSickScore = score;
@@ -138,7 +142,7 @@ public class GameStateEvaluator {
debugPrint("Score = " + score);
return new Score(score, summonSickScore);
}
-
+
public int evalCard(Game game, Player aiPlayer, Card c) {
// TODO: These should be based on other considerations - e.g. in relation to opponents state.
if (c.isCreature()) {
@@ -178,12 +182,12 @@ public class GameStateEvaluator {
this.value = value;
this.summonSickValue = value;
}
-
+
public Score(int value, int summonSickValue) {
this.value = value;
this.summonSickValue = summonSickValue;
}
-
+
public boolean equals(Score other) {
if (other == null)
return false;
diff --git a/forge-ai/src/main/java/forge/ai/simulation/Plan.java b/forge-ai/src/main/java/forge/ai/simulation/Plan.java
index d89d9009641..80b50e3cde3 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/Plan.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/Plan.java
@@ -22,7 +22,7 @@ public class Plan {
this.decisions = decisions;
this.finalScore = finalScore;
}
-
+
public Score getFinalScore() {
return finalScore;
}
@@ -102,6 +102,7 @@ public class Plan {
final Score initialScore;
final SpellAbilityRef saRef;
+ Integer xMana;
MultiTargetSelector.Targets targets;
List choices;
int[] modes;
@@ -112,14 +113,14 @@ public class Plan {
this.prevDecision = prevDecision;
this.saRef = saRef;
}
-
+
public Decision(Score initialScore, Decision prevDecision, MultiTargetSelector.Targets targets) {
this.initialScore = initialScore;
this.prevDecision = prevDecision;
this.saRef = null;
this.targets = targets;
}
-
+
public Decision(Score initialScore, Decision prevDecision, Card choice) {
this.initialScore = initialScore;
this.prevDecision = prevDecision;
@@ -144,7 +145,11 @@ public class Plan {
if (modesStr != null) {
sb.append(modesStr);
} else {
- sb.append(saRef.toString(showHostCard));
+ String sa = saRef.toString(showHostCard);
+ if (xMana != null) {
+ sa = sa.replace("(X=0)", "(X=" + xMana + ")");
+ }
+ sb.append(sa);
}
if (targets != null) {
sb.append(" (targets: ").append(targets).append(")");
diff --git a/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java b/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java
index 18e158e9a23..10ad14f8c3d 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/PossibleTargetSelector.java
@@ -167,6 +167,7 @@ public class PossibleTargetSelector {
private void selectTargetsByIndexImpl(int index) {
targetingSa.resetTargets();
+ // TODO this currently checks from max amount to just one but it doesn't check all combinations
while (targetingSa.getTargets().size() < maxTargets && index < validTargets.size()) {
targetingSa.getTargets().add(validTargets.get(index++));
}
diff --git a/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java b/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java
index 0bf94982686..5c9b19ddeb3 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/SimulationController.java
@@ -48,30 +48,30 @@ public class SimulationController {
private int getRecursionDepth() {
return scoreStack.size() - 1;
}
-
+
public boolean shouldRecurse() {
return bestScore.value != Integer.MAX_VALUE && getRecursionDepth() < MAX_DEPTH;
}
-
- private Plan.Decision getLastDecision() {
+
+ public Plan.Decision getLastDecision() {
if (currentStack.isEmpty()) {
return null;
}
return currentStack.get(currentStack.size() - 1);
}
-
+
private Score getCurrentScore() {
return scoreStack.get(scoreStack.size() - 1);
}
-
+
public void evaluateSpellAbility(List saList, int saIndex) {
currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), new Plan.SpellAbilityRef(saList, saIndex)));
}
-
+
public void evaluateCardChoice(Card choice) {
currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), choice));
}
-
+
public void evaluateChosenModes(int[] chosenModes, String modesStr) {
currentStack.add(new Plan.Decision(getCurrentScore(), getLastDecision(), chosenModes, modesStr));
}
@@ -87,11 +87,11 @@ public class SimulationController {
}
currentStack.remove(currentStack.size() - 1);
}
-
+
public Score getBestScore() {
return bestScore;
}
-
+
public Plan getBestPlan() {
if (!currentStack.isEmpty()) {
throw new RuntimeException("getBestPlan() expects currentStack to be empty!");
@@ -155,6 +155,7 @@ public class SimulationController {
}
merged.modes = modes;
merged.modesStr = modesStr;
+ merged.xMana = d.xMana;
return merged;
}
diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java
index b940278d477..01431db679c 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java
@@ -8,6 +8,7 @@ import java.util.NoSuchElementException;
import org.apache.commons.math3.util.CombinatoricsUtils;
+import forge.ai.ComputerUtilCost;
import forge.ai.simulation.GameStateEvaluator.Score;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -220,6 +221,16 @@ public class SpellAbilityChoicesIterator {
return modes;
}
+ public void announceX(SpellAbility sa) {
+ // TODO this should also iterate over all possible values
+ // (currently no additional complexity to keep performance reasonable)
+ if (sa.costHasManaX()) {
+ Integer x = ComputerUtilCost.getMaxXValue(sa, sa.getActivatingPlayer());
+ sa.setXManaCostPaid(x);
+ controller.getLastDecision().xMana = x;
+ }
+ }
+
private static class AllowRepeatModesIterator implements Iterator {
private int numChoices;
private int max;
diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java
index 412f2bd8495..11120549c47 100644
--- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java
+++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java
@@ -46,7 +46,7 @@ public class SpellAbilityPicker {
public void setInterceptor(SpellAbilityChoicesIterator in) {
this.interceptor = in;
}
-
+
private void print(String str) {
if (printOutput) {
System.out.println(str);
@@ -60,7 +60,7 @@ public class SpellAbilityPicker {
}
print("---- choose ability (phase = " + phaseStr + ")");
}
-
+
private List getCandidateSpellsAndAbilities() {
CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player);
List all = ComputerUtilAbility.getSpellAbilities(cards, player);
@@ -85,12 +85,12 @@ public class SpellAbilityPicker {
continue;
}
sa.setActivatingPlayer(player);
-
+
AiPlayDecision opinion = canPlayAndPayForSim(sa);
// print(" " + opinion + ": " + sa);
// PhaseHandler ph = game.getPhaseHandler();
// System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase());
-
+
if (opinion != AiPlayDecision.WillPlay)
continue;
candidateSAs.set(writeIndex, sa);
@@ -101,7 +101,7 @@ public class SpellAbilityPicker {
}
public SpellAbility chooseSpellAbilityToPlay(SimulationController controller) {
- printOutput = (controller == null);
+ printOutput = controller == null;
// Pass if top of stack is owned by me.
if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) {
@@ -263,10 +263,13 @@ public class SpellAbilityPicker {
return null;
}
}
+ if (decision.xMana != null) {
+ sa.setXManaCostPaid(decision.xMana);
+ }
print("Planned decision " + plan.getNextDecisionIndex() + ": " + decision);
return sa;
}
-
+
public Score getScoreForChosenAbility() {
return bestScore;
}
@@ -338,7 +341,7 @@ public class SpellAbilityPicker {
return AiPlayDecision.CantPlaySa;
}
- // Note: Can'tjust check condition on the top ability, because it may have
+ // Note: Can't just check condition on the top ability, because it may have
// sub-abilities without conditions (e.g. wild slash's main ability has a
// main ability with conditions but the burn sub-ability has none).
if (!atLeastOneConditionMet(sa)) {
@@ -394,7 +397,7 @@ public class SpellAbilityPicker {
}
return null;
}
-
+
private Card getPlannedChoice(CardCollection fetchList) {
// TODO: Make the below more robust?
if (plan != null && plan.getSelectedDecision() != null) {
diff --git a/forge-core/src/main/java/forge/card/CardChangedType.java b/forge-core/src/main/java/forge/card/CardChangedType.java
index 6e855548598..003e41cc47c 100644
--- a/forge-core/src/main/java/forge/card/CardChangedType.java
+++ b/forge-core/src/main/java/forge/card/CardChangedType.java
@@ -29,6 +29,7 @@ public class CardChangedType {
// takes care of individual card types
private final CardType addType;
private final CardType removeType;
+ private final boolean addAllCreatureTypes;
private final boolean removeSuperTypes;
private final boolean removeCardTypes;
private final boolean removeSubTypes;
@@ -37,12 +38,12 @@ public class CardChangedType {
private final boolean removeArtifactTypes;
private final boolean removeEnchantmentTypes;
- public CardChangedType(final CardType addType0, final CardType removeType0, final boolean removeSuperType0,
- final boolean removeCardType0, final boolean removeSubType0, final boolean removeLandType0,
- final boolean removeCreatureType0, final boolean removeArtifactType0,
- final boolean removeEnchantmentTypes0) {
+ public CardChangedType(final CardType addType0, final CardType removeType0, final boolean addAllCreatureTypes0,
+ final boolean removeSuperType0, final boolean removeCardType0, final boolean removeSubType0,
+ final boolean removeLandType0, final boolean removeCreatureType0, final boolean removeArtifactType0, final boolean removeEnchantmentTypes0) {
addType = addType0;
removeType = removeType0;
+ addAllCreatureTypes = addAllCreatureTypes0;
removeSuperTypes = removeSuperType0;
removeCardTypes = removeCardType0;
removeSubTypes = removeSubType0;
@@ -60,6 +61,10 @@ public class CardChangedType {
return removeType;
}
+ public final boolean isAddAllCreatureTypes() {
+ return addAllCreatureTypes;
+ }
+
public final boolean isRemoveSuperTypes() {
return removeSuperTypes;
}
diff --git a/forge-core/src/main/java/forge/card/CardType.java b/forge-core/src/main/java/forge/card/CardType.java
index 0a332ac2371..8dd5661b28c 100644
--- a/forge-core/src/main/java/forge/card/CardType.java
+++ b/forge-core/src/main/java/forge/card/CardType.java
@@ -51,8 +51,6 @@ public final class CardType implements Comparable, CardTypeView {
public static final CardTypeView EMPTY = new CardType(false);
- public static final String AllCreatureTypes = "AllCreatureTypes";
-
public enum CoreType {
Artifact(true, "artifacts"),
Conspiracy(false, "conspiracies"),
@@ -113,6 +111,9 @@ public final class CardType implements Comparable, CardTypeView {
private final Set coreTypes = EnumSet.noneOf(CoreType.class);
private final Set supertypes = EnumSet.noneOf(Supertype.class);
private final Set subtypes = Sets.newLinkedHashSet();
+ private boolean allCreatureTypes = false;
+ private final Set excludedCreatureSubtypes = Sets.newLinkedHashSet();
+
private boolean incomplete = false;
private transient String calculatedType = null;
@@ -125,6 +126,8 @@ public final class CardType implements Comparable, CardTypeView {
}
public CardType(final CardType from0) {
addAll(from0);
+ allCreatureTypes = from0.allCreatureTypes;
+ excludedCreatureSubtypes.addAll(from0.excludedCreatureSubtypes);
}
public CardType(final CardTypeView from0) {
addAll(from0);
@@ -153,6 +156,9 @@ public final class CardType implements Comparable, CardTypeView {
return false;
}
public boolean addAll(final Iterable types) {
+ if (types == null) {
+ return false;
+ }
boolean changed = false;
for (final String t : types) {
if (add(t)) {
@@ -235,16 +241,17 @@ public final class CardType implements Comparable, CardTypeView {
}
boolean changed = Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
// need to remove AllCreatureTypes too when setting Creature Type
- if (subtypes.remove(AllCreatureTypes)) {
+ if (allCreatureTypes) {
changed = true;
}
+ allCreatureTypes = false;
subtypes.addAll(ctypes);
return changed;
}
@Override
public boolean isEmpty() {
- return coreTypes.isEmpty() && supertypes.isEmpty() && subtypes.isEmpty();
+ return coreTypes.isEmpty() && supertypes.isEmpty() && subtypes.isEmpty() && !excludedCreatureSubtypes.isEmpty();
}
@Override
@@ -259,18 +266,29 @@ public final class CardType implements Comparable, CardTypeView {
public Iterable getSubtypes() {
return subtypes;
}
+
+ @Override
+ public Iterable getExcludedCreatureSubTypes() {
+ return excludedCreatureSubtypes;
+ }
+
@Override
public Set getCreatureTypes() {
final Set creatureTypes = Sets.newHashSet();
- if (isCreature() || isTribal()) {
- for (final String t : subtypes) {
- if (isACreatureType(t) || t.equals(AllCreatureTypes)) {
- creatureTypes.add(t);
- }
+ if (!isCreature() && !isTribal()) {
+ return creatureTypes;
+ }
+ if (hasAllCreatureTypes()) { // it should return list of all creature types
+ creatureTypes.addAll(getAllCreatureTypes());
+ creatureTypes.removeAll(this.excludedCreatureSubtypes);
+ } else {
+ for (final String t : Iterables.filter(subtypes, Predicates.IS_CREATURE_TYPE)) {
+ creatureTypes.add(t);
}
}
return creatureTypes;
}
+
@Override
public Set getLandTypes() {
final Set landTypes = Sets.newHashSet();
@@ -304,17 +322,26 @@ public final class CardType implements Comparable, CardTypeView {
}
return false;
}
+
@Override
public boolean hasType(final CoreType type) {
return coreTypes.contains(type);
}
+
@Override
public boolean hasSupertype(final Supertype supertype) {
return supertypes.contains(supertype);
}
+
+ @Override
+ public boolean hasAllCreatureTypes() {
+ if (!isCreature() && !isTribal()) { return false; }
+ return this.allCreatureTypes;
+ }
+
@Override
public boolean hasSubtype(final String subtype) {
- if (isACreatureType(subtype) && subtypes.contains(AllCreatureTypes)) {
+ if (hasCreatureType(subtype)) {
return true;
}
return subtypes.contains(subtype);
@@ -327,7 +354,13 @@ public final class CardType implements Comparable, CardTypeView {
creatureType = toMixedCase(creatureType);
if (!isACreatureType(creatureType)) { return false; }
- return subtypes.contains(creatureType) || subtypes.contains(AllCreatureTypes);
+ if (excludedCreatureSubtypes.contains(creatureType)) {
+ return false;
+ }
+ if (allCreatureTypes) {
+ return true;
+ }
+ return subtypes.contains(creatureType);
}
private static String toMixedCase(final String s) {
if (s.isEmpty()) {
@@ -449,11 +482,25 @@ public final class CardType implements Comparable, CardTypeView {
@Override
public String toString() {
if (calculatedType == null) {
- if (subtypes.isEmpty()) {
- calculatedType = StringUtils.join(getTypesBeforeDash(), ' ');
- } else {
- calculatedType = StringUtils.join(getTypesBeforeDash(), ' ') + " - " + StringUtils.join(subtypes, " ");
+ StringBuilder sb = new StringBuilder(StringUtils.join(getTypesBeforeDash(), ' '));
+ if (!subtypes.isEmpty() || hasAllCreatureTypes()) {
+ sb.append(" - ");
}
+ if (!subtypes.isEmpty()) {
+ sb.append(StringUtils.join(subtypes, " "));
+ }
+ if (hasAllCreatureTypes()) {
+ if (!subtypes.isEmpty()) {
+ sb.append(" ");
+ }
+ sb.append("(All");
+ if (!excludedCreatureSubtypes.isEmpty()) {
+ sb.append(" except ").append(StringUtils.join(excludedCreatureSubtypes, " "));
+ }
+ sb.append(")");
+ }
+
+ calculatedType = sb.toString();
}
return calculatedType;
}
@@ -496,7 +543,7 @@ public final class CardType implements Comparable, CardTypeView {
if (ct.isRemoveCreatureTypes()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_CREATURE_TYPE);
// need to remove AllCreatureTypes too when removing creature Types
- newType.subtypes.remove(AllCreatureTypes);
+ newType.allCreatureTypes = false;
}
if (ct.isRemoveArtifactTypes()) {
Iterables.removeIf(newType.subtypes, Predicates.IS_ARTIFACT_TYPE);
@@ -510,6 +557,16 @@ public final class CardType implements Comparable, CardTypeView {
}
if (ct.getAddType() != null) {
newType.addAll(ct.getAddType());
+ if (ct.getAddType().hasAllCreatureTypes()) {
+ newType.allCreatureTypes = true;
+ }
+ }
+ if (ct.isAddAllCreatureTypes()) {
+ newType.allCreatureTypes = true;
+ }
+ // remove specific creature types from all creature types
+ if (ct.getRemoveType() != null && newType.allCreatureTypes) {
+ newType.excludedCreatureSubtypes.addAll(Lists.newArrayList(Iterables.filter(ct.getRemoveType(), Predicates.IS_CREATURE_TYPE)));
}
}
// sanisfy subtypes
@@ -524,9 +581,14 @@ public final class CardType implements Comparable, CardTypeView {
if (this.incomplete) {
return;
}
+ if (!isCreature() && !isTribal()) {
+ allCreatureTypes = false;
+ }
+ if (subtypes.isEmpty()) {
+ return;
+ }
if (!isCreature() && !isTribal()) {
Iterables.removeIf(subtypes, Predicates.IS_CREATURE_TYPE);
- subtypes.remove(AllCreatureTypes);
}
if (!isLand()) {
Iterables.removeIf(subtypes, Predicates.IS_LAND_TYPE);
@@ -583,9 +645,21 @@ public final class CardType implements Comparable, CardTypeView {
if (ctOther == null) {
return false;
}
- if (this.subtypes.contains(AllCreatureTypes) && ctOther.hasSubtype(AllCreatureTypes)) {
- return true;
+ if (!isCreature() && !isTribal()) {
+ return false;
}
+ if (!ctOther.isCreature() && !ctOther.isTribal()) {
+ return false;
+ }
+
+ // special cases for if any of them is all creature types
+ if (this.allCreatureTypes && ctOther.hasAllCreatureTypes()) {
+ // no type is exluded so they should share all creature types
+ if (excludedCreatureSubtypes.isEmpty() && Iterables.isEmpty(ctOther.getExcludedCreatureSubTypes())) {
+ return true;
+ }
+ }
+
for (final String type : getCreatureTypes()) {
if (ctOther.hasCreatureType(type)) {
return true;
@@ -642,6 +716,9 @@ public final class CardType implements Comparable, CardTypeView {
if (ctOther == null) {
return false;
}
+ if (sharesCreaturetypeWith(ctOther)) {
+ return true;
+ }
for (final String t : ctOther.getSubtypes()) {
if (hasSubtype(t)) {
return true;
@@ -722,7 +799,12 @@ public final class CardType implements Comparable, CardTypeView {
return CardType.isALandType(input);
}
};
-
+ public static Predicate IS_BASIC_LAND_TYPE = new Predicate() {
+ @Override
+ public boolean apply(String input) {
+ return CardType.isABasicLandType(input);
+ }
+ };
public static Predicate IS_ARTIFACT_TYPE = new Predicate() {
@Override
public boolean apply(String input) {
diff --git a/forge-core/src/main/java/forge/card/CardTypeView.java b/forge-core/src/main/java/forge/card/CardTypeView.java
index f89452baac8..4c27266e72a 100644
--- a/forge-core/src/main/java/forge/card/CardTypeView.java
+++ b/forge-core/src/main/java/forge/card/CardTypeView.java
@@ -12,13 +12,18 @@ public interface CardTypeView extends Iterable, Serializable {
Iterable getCoreTypes();
Iterable getSupertypes();
Iterable getSubtypes();
+ Iterable getExcludedCreatureSubTypes();
+
Set getCreatureTypes();
Set getLandTypes();
+
+
boolean hasStringType(String t);
boolean hasType(CoreType type);
boolean hasSupertype(Supertype supertype);
boolean hasSubtype(String subtype);
boolean hasCreatureType(String creatureType);
+ boolean hasAllCreatureTypes();
public boolean sharesCreaturetypeWith(final CardTypeView ctOther);
public boolean sharesLandTypeWith(final CardTypeView ctOther);
diff --git a/forge-core/src/main/java/forge/deck/generation/DeckGeneratorBase.java b/forge-core/src/main/java/forge/deck/generation/DeckGeneratorBase.java
index 0aa65acf1c3..7eb4291f684 100644
--- a/forge-core/src/main/java/forge/deck/generation/DeckGeneratorBase.java
+++ b/forge-core/src/main/java/forge/deck/generation/DeckGeneratorBase.java
@@ -92,7 +92,6 @@ public abstract class DeckGeneratorBase {
final Iterable cards = selectCardsOfMatchingColorForPlayer(forAi);
// build subsets based on type
-
final Iterable creatures = Iterables.filter(cards, Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, PaperCard.FN_GET_RULES));
final int creatCnt = (int) Math.ceil(getCreaturePercentage() * size);
trace.append("Creatures to add:").append(creatCnt).append("\n");
diff --git a/forge-core/src/main/java/forge/util/lang/LangGerman.java b/forge-core/src/main/java/forge/util/lang/LangGerman.java
index 3c9d6dc1674..30558194d07 100644
--- a/forge-core/src/main/java/forge/util/lang/LangGerman.java
+++ b/forge-core/src/main/java/forge/util/lang/LangGerman.java
@@ -26,7 +26,6 @@ public class LangGerman extends Lang {
return getPossesive(owner) + " " + object;
}
-
@Override
public String getNickName(final String name) {
if (name.contains(",")) {
diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java
index 4dbb76d9e7e..6183d6f2294 100644
--- a/forge-game/src/main/java/forge/game/ForgeScript.java
+++ b/forge-game/src/main/java/forge/game/ForgeScript.java
@@ -156,6 +156,8 @@ public class ForgeScript {
return sa.isKicked();
} else if (property.equals("Loyalty")) {
return sa.isPwAbility();
+ } else if (property.equals("nonLoyalty")) {
+ return !sa.isPwAbility();
} else if (property.equals("Aftermath")) {
return sa.isAftermath();
} else if (property.equals("MorphUp")) {
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index f158cfcb9be..56a06649083 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -355,7 +355,7 @@ public class GameAction {
ReplacementResult repres = game.getReplacementHandler().run(ReplacementType.Moved, repParams);
if (repres != ReplacementResult.NotReplaced) {
// reset failed manifested Cards back to original
- if (c.isManifested() && !c.isInZone(ZoneType.Battlefield)) {
+ if (c.isManifested() && !c.isInPlay()) {
c.forceTurnFaceUp();
}
@@ -373,7 +373,7 @@ public class GameAction {
}
// was replaced with another Zone Change
- if (toBattlefield && !c.isInZone(ZoneType.Battlefield)) {
+ if (toBattlefield && !c.isInPlay()) {
if (c.removeChangedState()) {
c.updateStateForView();
}
@@ -1171,7 +1171,7 @@ public class GameAction {
}
if (c.isCreature() && c.isPaired()) {
Card partner = c.getPairedWith();
- if (!partner.isCreature() || c.getController() != partner.getController() || !c.isInZone(ZoneType.Battlefield)) {
+ if (!partner.isCreature() || c.getController() != partner.getController() || !c.isInPlay()) {
c.setPairedWith(null);
partner.setPairedWith(null);
affectedCards.add(c);
diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java
index b3fecdba818..96a974d2179 100644
--- a/forge-game/src/main/java/forge/game/GameActionUtil.java
+++ b/forge-game/src/main/java/forge/game/GameActionUtil.java
@@ -96,7 +96,7 @@ public final class GameActionUtil {
Card source = sa.getHostCard();
final Game game = source.getGame();
- if (sa.isSpell() && !source.isInZone(ZoneType.Battlefield)) {
+ if (sa.isSpell() && !source.isInPlay()) {
boolean lkicheck = false;
Card newHost = ((Spell)sa).getAlternateHost(source);
diff --git a/forge-game/src/main/java/forge/game/GlobalRuleChange.java b/forge-game/src/main/java/forge/game/GlobalRuleChange.java
index d0381166b4b..2a02d696449 100644
--- a/forge-game/src/main/java/forge/game/GlobalRuleChange.java
+++ b/forge-game/src/main/java/forge/game/GlobalRuleChange.java
@@ -25,7 +25,6 @@ public enum GlobalRuleChange {
alwaysWither ("All damage is dealt as though its source had wither."),
attackerChoosesBlockers ("The attacking player chooses how each creature blocks each combat."),
manaBurn ("A player losing unspent mana causes that player to lose that much life."),
- manapoolsDontEmpty ("Mana pools don't empty as steps and phases end."),
noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."),
noCreatureDyingTriggers ("Creatures dying don't cause abilities to trigger."),
noLegendRule ("The legend rule doesn't apply."),
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
index 603c4cc99a0..a8bda73a098 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
@@ -1689,10 +1689,6 @@ public class AbilityUtils {
// accept straight numbers
if (l[0].startsWith("Number$")) {
final String number = l[0].substring(7);
- if (number.equals("ChosenNumber")) { // TODO remove in favor of Count ChosenNumber
- int x = c.getChosenNumber() == null ? 0 : c.getChosenNumber();
- return doXMath(x, expr, c, ctb);
- }
return doXMath(Integer.parseInt(number), expr, c, ctb);
}
@@ -2471,11 +2467,11 @@ public class AbilityUtils {
// Figure out how to count each class separately.
for (Card card : adventurers) {
+ // cards with all creature types will just return full list
Set creatureTypes = card.getType().getCreatureTypes();
- boolean anyType = creatureTypes.contains(CardType.AllCreatureTypes);
creatureTypes.retainAll(partyTypes);
- if (anyType || creatureTypes.size() == 4) {
+ if (creatureTypes.size() == 4) {
wildcard.add(card);
if (wildcard.size() >= 4) {
@@ -2574,8 +2570,7 @@ public class AbilityUtils {
Iterables.addAll(creatTypes, card.getType().getCreatureTypes());
}
// filter out fun types?
- int n = creatTypes.contains(CardType.AllCreatureTypes) ? CardType.getAllCreatureTypes().size() : creatTypes.size();
- return doXMath(n, expr, c, ctb);
+ return doXMath(creatTypes.size(), expr, c, ctb);
}
// Count$Chroma.
@@ -2882,8 +2877,7 @@ public class AbilityUtils {
for (Card card : cards) {
Iterables.addAll(creatTypes, card.getType().getCreatureTypes());
}
- int n = creatTypes.contains(CardType.AllCreatureTypes) ? CardType.getAllCreatureTypes().size() : creatTypes.size();
- return doXMath(n, expr, c, ctb);
+ return doXMath(creatTypes.size(), expr, c, ctb);
}
// Complex counting methods
CardCollectionView someCards = getCardListForXCount(c, player, sq, ctb);
diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
index f0c98e4b62b..bd7cfb2ada7 100644
--- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java
@@ -135,9 +135,9 @@ public abstract class SpellAbilityEffect {
}
}
- String currentName = (sa.getHostCard().getName());
- String substitutedDesc = TextUtil.fastReplace(sb.toString(), "CARDNAME", CardTranslation.getTranslatedName(currentName));
- substitutedDesc = TextUtil.fastReplace(substitutedDesc, "NICKNAME", Lang.getInstance().getNickName(CardTranslation.getTranslatedName(currentName)));
+ String currentName = CardTranslation.getTranslatedName(sa.getHostCard().getName());
+ String substitutedDesc = TextUtil.fastReplace(sb.toString(), "CARDNAME", currentName);
+ substitutedDesc = TextUtil.fastReplace(substitutedDesc, "NICKNAME", Lang.getInstance().getNickName(currentName));
return substitutedDesc;
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java
index 6f7c310e910..3ec8a944860 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/AddPhaseEffect.java
@@ -49,7 +49,7 @@ public class AddPhaseEffect extends SpellAbilityEffect {
extraPhaseList.addAll(PhaseType.PHASE_GROUPS.get(0));
} else if (extra.equals("Combat")) {
extraPhaseList.addAll(PhaseType.PHASE_GROUPS.get(2));
- } else { // Currently no effect will add End Phase
+ } else { // Currently no effect will add End Phase
extraPhaseList.add(PhaseType.smartValueOf(extra));
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
index 853e2b616e3..0616ed3e244 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java
@@ -50,6 +50,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
final Card source = sa.getHostCard();
final Game game = source.getGame();
+ boolean addAllCreatureTypes = false;
boolean removeSuperTypes = false;
boolean removeCardTypes = false;
boolean removeSubTypes = false;
@@ -60,6 +61,10 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
boolean removeAll = sa.hasParam("RemoveAllAbilities");
+ if (sa.hasParam("AddAllCreatureTypes")) {
+ addAllCreatureTypes = true;
+ }
+
if (sa.hasParam("RemoveSuperTypes")) {
removeSuperTypes = true;
}
@@ -93,8 +98,9 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect {
c.addNewPT(power, toughness, timestamp, 0);
}
- if (!addType.isEmpty() || !removeType.isEmpty() || removeCreatureTypes) {
- c.addChangedCardTypes(addType, removeType, removeSuperTypes, removeCardTypes, removeSubTypes,
+ if (!addType.isEmpty() || !removeType.isEmpty() || addAllCreatureTypes || removeSuperTypes
+ || removeCardTypes || removeSubTypes || removeLandTypes || removeCreatureTypes || removeArtifactTypes || removeEnchantmentTypes) {
+ c.addChangedCardTypes(addType, removeType, addAllCreatureTypes, removeSuperTypes, removeCardTypes, removeSubTypes,
removeLandTypes, removeCreatureTypes, removeArtifactTypes, removeEnchantmentTypes, timestamp, 0, true, false);
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/BondEffect.java b/forge-game/src/main/java/forge/game/ability/effects/BondEffect.java
index 7a05d732a38..32814c577d3 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/BondEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/BondEffect.java
@@ -17,7 +17,7 @@ public class BondEffect extends SpellAbilityEffect {
CardCollectionView trigCards = AbilityUtils.getDefinedCards(sa.getHostCard(), sa.getParam("Defined"), sa);
// Check that this card hasn't already become paired by an earlier trigger
- if (trigCards.getFirst().isPaired() || !trigCards.getFirst().isInZone(ZoneType.Battlefield)) {
+ if (trigCards.getFirst().isPaired() || !trigCards.getFirst().isInPlay()) {
return;
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeTextEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeTextEffect.java
index d7174ab3bac..115b7bab66d 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChangeTextEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeTextEffect.java
@@ -70,7 +70,7 @@ public class ChangeTextEffect extends SpellAbilityEffect {
kindOfType = "basic land";
} else if (changedTypeWordsArray[0].equals("ChooseCreatureType")) {
validTypes.addAll(CardType.Constant.CREATURE_TYPES);
- kindOfType = "creature";
+ kindOfType = "Creature";
}
changedTypeWordOriginal = sa.getActivatingPlayer().getController().chooseSomeType(kindOfType, sa, validTypes, Lists.newArrayList());
} else {
@@ -86,7 +86,7 @@ public class ChangeTextEffect extends SpellAbilityEffect {
kindOfType = "basic land";
} else if (changedTypeWordsArray[1].equals("ChooseCreatureType")) {
validTypes.addAll(CardType.Constant.CREATURE_TYPES);
- kindOfType = "creature";
+ kindOfType = "Creature";
}
changedTypeWordNew = sa.getActivatingPlayer().getController().chooseSomeType(kindOfType, sa, validTypes, forbiddenTypes);
} else {
@@ -102,7 +102,7 @@ public class ChangeTextEffect extends SpellAbilityEffect {
if (changedColorWordOriginal != null && changedColorWordNew != null) {
c.addChangedTextColorWord(changedColorWordOriginal, changedColorWordNew, timestamp, 0);
}
- if (changedTypeWordOriginal != null && changedTypeWordNew != null ) {
+ if (changedTypeWordOriginal != null && changedTypeWordNew != null) {
c.addChangedTextTypeWord(changedTypeWordOriginal, changedTypeWordNew, timestamp, 0);
}
@@ -123,6 +123,8 @@ public class ChangeTextEffect extends SpellAbilityEffect {
}
game.fireEvent(new GameEventCardStatsChanged(c));
+ c.updateStateForView();
+ c.updateTypesForView();
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java
index 99ec1d54876..31fd27cb327 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java
@@ -272,8 +272,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
sb.append(".");
} else if (origin.equals("Battlefield")) {
// TODO Expand on this Description as more cards use it
- // for the non-targeted SAs when you choose what is returned on
- // resolution
+ // for the non-targeted SAs when you choose what is returned on resolution
sb.append("Return ").append(num).append(" ").append(type).append(" card(s) ");
sb.append(" to your ").append(destination);
}
@@ -361,8 +360,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
sb.append(fromGraveyard);
}
- // this needs to be zero indexed. Top = 0, Third = 2, -1 =
- // Bottom
+ // this needs to be zero indexed. Top = 0, Third = 2, -1 = Bottom
final int libraryPosition = sa.hasParam("LibraryPosition") ? AbilityUtils.calculateAmount(host, sa.getParam("LibraryPosition"), sa) : 0;
if (libraryPosition == -1) {
@@ -451,9 +449,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
sb.append(sa.getParam("AlternativeDestinationMessage"));
Player alterDecider = player;
if (sa.hasParam("AlternativeDecider")) {
- alterDecider = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("AlternativeDecider"), sa).get(0);
+ PlayerCollection deciders = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("AlternativeDecider"), sa);
+ alterDecider = deciders.isEmpty() ? null : deciders.get(0);
}
- if (!alterDecider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString())) {
+ if (alterDecider != null && !alterDecider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString())) {
destination = ZoneType.smartValueOf(sa.getParam("DestinationAlternative"));
altDest = true;
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java
index ec348aebae2..988e313275a 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java
@@ -33,17 +33,18 @@ public class CharmEffect extends SpellAbilityEffect {
}
}
- int indx = 0;
List choices = Lists.newArrayList(sa.getAdditionalAbilityList("Choices"));
- if (restriction != null) {
- List toRemove = Lists.newArrayList();
- for (AbilitySub ch : choices) {
- if (restriction.contains(ch.getDescription())) {
- toRemove.add(ch);
- }
+ List toRemove = Lists.newArrayList();
+ for (AbilitySub ch : choices) {
+ // 603.3c If one of the modes would be illegal, that mode can't be chosen.
+ if ((ch.usesTargeting() && ch.isTrigger() && ch.getTargetRestrictions().getNumCandidates(ch, true) == 0) ||
+ (restriction != null && restriction.contains(ch.getDescription()))) {
+ toRemove.add(ch);
}
- choices.removeAll(toRemove);
}
+ choices.removeAll(toRemove);
+
+ int indx = 0;
// set CharmOrder
for (AbilitySub sub : choices) {
sub.setSVar("CharmOrder", Integer.toString(indx));
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java
index a7f8585bea9..a328e38b68b 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java
@@ -127,7 +127,7 @@ public class ControlGainEffect extends SpellAbilityEffect {
}
// check for lose control criteria right away
- if (lose != null && lose.contains("LeavesPlay") && !source.isInZone(ZoneType.Battlefield)) {
+ if (lose != null && lose.contains("LeavesPlay") && !source.isInPlay()) {
return;
}
if (lose != null && lose.contains("Untap") && !source.isTapped()) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java
index a7c7c5ba6d6..f7d5b928985 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DeclareCombatantsEffect.java
@@ -32,18 +32,18 @@ public class DeclareCombatantsEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
List tgtPlayers = getDefinedPlayersOrTargeted(sa);
-
+
final boolean attackers = sa.hasParam("DeclareAttackers");
final boolean blockers = sa.hasParam("DeclareBlockers");
-
+
String until = sa.getParam("Until");
boolean untilEoT = "EndOfTurn".equals(until);
-
+
for (Player p : tgtPlayers) { // Obviously the last player will be applied
final PhaseHandler ph = p.getGame().getPhaseHandler();
if (attackers) ph.setPlayerDeclaresAttackers(p);
if (blockers) ph.setPlayerDeclaresBlockers(p);
-
+
GameCommand removeOverrides = new GameCommand() {
private static final long serialVersionUID = -8064627517852651016L;
@@ -53,7 +53,7 @@ public class DeclareCombatantsEffect extends SpellAbilityEffect {
if (blockers) ph.setPlayerDeclaresBlockers(null);
}
};
-
+
if (untilEoT)
p.getGame().getEndOfTurn().addUntil(removeOverrides);
else
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java
index 8c47f586c53..9adec6a686e 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DelayedTriggerEffect.java
@@ -56,11 +56,7 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
if (sa.hasParam("RememberObjects")) {
for (final String rem : sa.getParam("RememberObjects").split(",")) {
- for (final Object o : AbilityUtils.getDefinedObjects(sa.getHostCard(), rem, sa)) {
- if (o instanceof SpellAbility) {
- // "RememberObjects$ Remembered" don't remember spellability
- continue;
- }
+ for (final Object o : AbilityUtils.getDefinedEntities(sa.getHostCard(), rem, sa)) {
delTrig.addRemembered(o);
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java
index fe82dc1738c..1fb6c9616a3 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java
@@ -81,7 +81,7 @@ public class EffectEffect extends SpellAbilityEffect {
if (sa.hasParam("RememberObjects")) {
rememberList = new FCollection<>();
for (final String rem : sa.getParam("RememberObjects").split(",")) {
- rememberList.addAll(AbilityUtils.getDefinedObjects(hostCard, rem, sa));
+ rememberList.addAll(AbilityUtils.getDefinedEntities(hostCard, rem, sa));
}
if (sa.hasParam("ForgetCounter")) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java
index cee5d82d45a..d2410394a6a 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ImmediateTriggerEffect.java
@@ -28,7 +28,6 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
}
return "";
-
}
@Override
@@ -60,11 +59,7 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
if (sa.hasParam("RememberObjects")) {
for (final String rem : sa.getParam("RememberObjects").split(",")) {
- for (final Object o : AbilityUtils.getDefinedObjects(sa.getHostCard(), rem, sa)) {
- if (o instanceof SpellAbility) {
- // "RememberObjects$ Remembered" don't remember spellability
- continue;
- }
+ for (final Object o : AbilityUtils.getDefinedEntities(sa.getHostCard(), rem, sa)) {
immediateTrig.addRemembered(o);
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java
index 44d33726de5..8839022cb69 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java
@@ -17,9 +17,9 @@ import forge.game.event.GameEventCardStatsChanged;
import forge.game.player.PlayerCollection;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
-import forge.util.TextUtil;
public class PumpAllEffect extends SpellAbilityEffect {
+
private static void applyPumpAll(final SpellAbility sa,
final Iterable list, final int a, final int d,
final List keywords, final List affectedZones) {
@@ -110,7 +110,7 @@ public class PumpAllEffect extends SpellAbilityEffect {
String desc = "";
if (sa.hasParam("SpellDescription")) {
- desc = TextUtil.fastReplace(sa.getParam("SpellDescription"), "CARDNAME", sa.getHostCard().getName());
+ desc = sa.getParam("SpellDescription");
}
sb.append(desc);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java
index 2c949fa94dd..da7d6c479e5 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java
@@ -16,7 +16,6 @@ import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
-import forge.util.TextUtil;
import forge.util.collect.FCollectionView;
public class RestartGameEffect extends SpellAbilityEffect {
@@ -110,6 +109,6 @@ public class RestartGameEffect extends SpellAbilityEffect {
desc = "Restart the game.";
}
- return TextUtil.fastReplace(desc, "CARDNAME", sa.getHostCard().getName());
+ return desc;
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java
index a251d86ca6f..c1ee740946f 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java
@@ -87,12 +87,12 @@ public class SetStateEffect extends SpellAbilityEffect {
// Cards which are not on the battlefield should not be able to transform.
// TurnFace should be allowed in other zones like Exile too
- if (!"TurnFace".equals(mode) && !gameCard.isInZone(ZoneType.Battlefield) && !sa.hasParam("ETB")) {
+ if (!"TurnFace".equals(mode) && !gameCard.isInPlay() && !sa.hasParam("ETB")) {
continue;
}
// facedown cards that are not Permanent, can't turn faceup there
- if ("TurnFace".equals(mode) && gameCard.isFaceDown() && gameCard.isInZone(ZoneType.Battlefield)) {
+ if ("TurnFace".equals(mode) && gameCard.isFaceDown() && gameCard.isInPlay()) {
if (gameCard.hasMergedCard()) {
boolean hasNonPermanent = false;
Card nonPermanentCard = null;
@@ -119,7 +119,7 @@ public class SetStateEffect extends SpellAbilityEffect {
}
// Merged faceup permanent that have double faced cards can't turn face down
- if ("TurnFace".equals(mode) && !gameCard.isFaceDown() && gameCard.isInZone(ZoneType.Battlefield)
+ if ("TurnFace".equals(mode) && !gameCard.isFaceDown() && gameCard.isInPlay()
&& gameCard.hasMergedCard()) {
boolean hasBackSide = false;
for (final Card c : gameCard.getMergedCards()) {
diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java
index be2815f6001..cc9b8b24bbe 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -1478,7 +1478,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
long timestamp = game.getNextTimestamp();
counterTypeTimestamps.put(counterType, timestamp);
// becomes land in instead of other card types
- addChangedCardTypes(new CardType(ImmutableList.of("Land"), false), null, false, true, true, false, false, false, false, timestamp, 0, updateView, false);
+ addChangedCardTypes(new CardType(ImmutableList.of("Land"), false), null, false, false, true, true, false, false, false, false, timestamp, 0, updateView, false);
String abStr = "AB$ ManaReflected | Cost$ T | Valid$ Defined.Self | ColorOrType$ Color | ReflectProperty$ Is | SpellDescription$ Add one mana of any of this card's colors.";
@@ -2408,7 +2408,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
// CantBlockBy static abilities
- if (game != null && isCreature() && isInZone(ZoneType.Battlefield)) {
+ if (game != null && isCreature() && isInPlay()) {
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
if (equals(ca)) {
continue;
@@ -2453,7 +2453,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
sb.append(linebreak);
// Currently the maximum levels of all Class cards are all 3
for (int level = 1; level <= 3; ++level) {
- boolean disabled = level > getClassLevel() && isInZone(ZoneType.Battlefield);
+ boolean disabled = level > getClassLevel() && isInPlay();
// Class second part is a static ability that grants the other abilities
for (final StaticAbility st : state.getStaticAbilities()) {
if (st.isClassLevelNAbility(level) && !st.isSecondary()) {
@@ -2880,7 +2880,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
// add Facedown abilities from Original state but only if this state is face down
// need CardStateView#getState or might crash in StackOverflow
- if (isInZone(ZoneType.Battlefield)) {
+ if (isInPlay()) {
if ((null == mana || false == mana) && isFaceDown() && state.getView().getState() == CardStateName.FaceDown) {
for (SpellAbility sa : getState(CardStateName.Original).getNonManaAbilities()) {
if (sa.isManifestUp() || sa.isMorphUp()) {
@@ -3009,7 +3009,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
// is this "Card" supposed to be a token?
public final boolean isToken() {
- if (isInZone(ZoneType.Battlefield) && hasMergedCard()) {
+ if (isInPlay() && hasMergedCard()) {
return getTopMergedCard().token;
}
return token;
@@ -3027,7 +3027,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
public final boolean isTokenCard() {
- if (isInZone(ZoneType.Battlefield) && hasMergedCard()) {
+ if (isInPlay() && hasMergedCard()) {
return getTopMergedCard().tokenCard;
}
return tokenCard;
@@ -3533,12 +3533,13 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
return getType(currentState);
}
public final CardTypeView getType(CardState state) {
- if (changedCardTypes.isEmpty() && changedCardTypesCharacterDefining.isEmpty()) {
+ final Iterable changedCardTypes = getChangedCardTypes();
+ if (Iterables.isEmpty(changedCardTypes)) {
return state.getType();
}
// CR 506.4 attacked planeswalkers leave combat
boolean checkCombat = state.getType().isPlaneswalker() && game.getCombat() != null && !game.getCombat().getAttackersOf(this).isEmpty();
- CardTypeView types = state.getType().getTypeWithChanges(getChangedCardTypes());
+ CardTypeView types = state.getType().getTypeWithChanges(changedCardTypes);
if (checkCombat && !types.isPlaneswalker()) {
game.getCombat().removeFromCombat(this);
}
@@ -3548,14 +3549,12 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
public final CardTypeView getOriginalType() {
return getOriginalType(currentState);
}
-
public final CardTypeView getOriginalType(CardState state) {
return state.getType();
}
// TODO add changed type by card text
public Iterable getChangedCardTypes() {
-
Iterable byText = changedTypeByText == null ? ImmutableList.of() : ImmutableList.of(this.changedTypeByText);
return Iterables.unmodifiableIterable(Iterables.concat(
@@ -3563,7 +3562,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
byText, // Layer 3 by Word Changes,
changedCardTypesCharacterDefining.values(), // Layer 4
changedCardTypes.values() // Layer 6
- ));
+ ));
}
public Table getChangedCardTypesTable() {
@@ -3618,7 +3617,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
public Iterable getChangedCardKeywordsList() {
-
return Iterables.concat(
changedCardKeywordsByText.values(), // Layer 3
ImmutableList.of(new KeywordsChange(ImmutableList.of(), null, this.hasRemoveIntrinsic())), // Layer 4
@@ -3630,7 +3628,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
return changedCardKeywords;
}
-
public Table getChangedCardColorsTable() {
return changedCardColors;
}
@@ -3646,7 +3643,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
public final void addChangedCardTypesByText(final CardType addType, final long timestamp, final long staticId, final boolean updateView) {
- changedCardTypesByText.put(timestamp, staticId, new CardChangedType(addType, null, true, true, true, false, false, false, false));
+ changedCardTypesByText.put(timestamp, staticId, new CardChangedType(addType, null, false, true, true, true, false, false, false, false));
// setting card type via text, does overwrite any other word change effects?
this.changedTextColors.addEmpty(timestamp, staticId);
@@ -3657,20 +3654,20 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
}
- public final void addChangedCardTypes(final CardType addType, final CardType removeType,
+ public final void addChangedCardTypes(final CardType addType, final CardType removeType, final boolean addAllCreatureTypes,
final boolean removeSuperTypes, final boolean removeCardTypes, final boolean removeSubTypes,
final boolean removeLandTypes, final boolean removeCreatureTypes, final boolean removeArtifactTypes,
final boolean removeEnchantmentTypes,
final long timestamp, final long staticId, final boolean updateView, final boolean cda) {
(cda ? changedCardTypesCharacterDefining : changedCardTypes).put(timestamp, staticId, new CardChangedType(
- addType, removeType, removeSuperTypes, removeCardTypes, removeSubTypes,
+ addType, removeType, addAllCreatureTypes, removeSuperTypes, removeCardTypes, removeSubTypes,
removeLandTypes, removeCreatureTypes, removeArtifactTypes, removeEnchantmentTypes));
if (updateView) {
updateTypesForView();
}
}
- public final void addChangedCardTypes(final Iterable types, final Iterable removeTypes,
+ public final void addChangedCardTypes(final Iterable types, final Iterable removeTypes, final boolean addAllCreatureTypes,
final boolean removeSuperTypes, final boolean removeCardTypes, final boolean removeSubTypes,
final boolean removeLandTypes, final boolean removeCreatureTypes, final boolean removeArtifactTypes,
final boolean removeEnchantmentTypes,
@@ -3685,7 +3682,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
removeType = new CardType(removeTypes, true);
}
- addChangedCardTypes(addType, removeType, removeSuperTypes, removeCardTypes, removeSubTypes,
+ addChangedCardTypes(addType, removeType, addAllCreatureTypes, removeSuperTypes, removeCardTypes, removeSubTypes,
removeLandTypes, removeCreatureTypes, removeArtifactTypes, removeEnchantmentTypes,
timestamp, staticId, updateView, cda);
}
@@ -4457,7 +4454,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
}
- this.changedTypeByText = new CardChangedType(new CardType(toAdd, true), new CardType(toRemove, true), false, false, false, false, false, false, false);
+ this.changedTypeByText = new CardChangedType(new CardType(toAdd, true), new CardType(toRemove, true), false, false, false, false, false, false, false, false);
currentState.updateChangedText();
@@ -4474,7 +4471,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
final List addKeywords = Lists.newArrayList();
final List removeKeywords = Lists.newArrayList();
// Text Change for intrinsic keywords
- for(KeywordInterface kw : beforeKeywords) {
+ for (KeywordInterface kw : beforeKeywords) {
String oldtxt = kw.getOriginal();
final String newtxt = AbilityUtils.applyKeywordTextChangeEffects(oldtxt, this);
if (!newtxt.equals(oldtxt)) {
@@ -4562,6 +4559,9 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
public final Iterable getHiddenExtrinsicKeywords() {
return Iterables.concat(this.hiddenExtrinsicKeywords.values());
}
+ public final Table> getHiddenExtrinsicKeywordsTable() {
+ return hiddenExtrinsicKeywords;
+ }
public final void addHiddenExtrinsicKeywords(long timestamp, long staticId, Iterable keywords) {
// TODO if some keywords aren't removed anymore, then no need for extra Array List
@@ -4660,11 +4660,11 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
public final boolean isPermanent() {
- return !isImmutable() && (isInZone(ZoneType.Battlefield) || getType().isPermanent());
+ return !isImmutable() && (isInPlay() || getType().isPermanent());
}
public final boolean isSpell() {
- return (isInstant() || isSorcery() || (isAura() && !isInZone((ZoneType.Battlefield))));
+ return isInstant() || isSorcery() || (isAura() && !isInZone((ZoneType.Battlefield)));
}
public final boolean isLand() { return getType().isLand(); }
@@ -5139,12 +5139,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
public final boolean hasABasicLandType() {
- for (final String type : getType().getSubtypes()) {
- if (forge.card.CardType.isABasicLandType(type)) {
- return true;
- }
- }
- return false;
+ return Iterables.any(getType().getSubtypes(), CardType.Predicates.IS_BASIC_LAND_TYPE);
}
public final boolean isUsedToPay() {
@@ -5639,7 +5634,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
bestowTimestamp = getGame().getNextTimestamp();
addChangedCardTypes(new CardType(Collections.singletonList("Aura"), true),
new CardType(Collections.singletonList("Creature"), true),
- false, false, false, false, false, false, true, bestowTimestamp, 0, updateView, false);
+ false, false, false, false, false, false, false, true, bestowTimestamp, 0, updateView, false);
addChangedCardKeywords(Collections.singletonList("Enchant creature"), Lists.newArrayList(),
false, bestowTimestamp, 0, updateView);
}
@@ -5749,7 +5744,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
// Protection only works on the Battlefield
- if (!isInZone(ZoneType.Battlefield)) {
+ if (!isInPlay()) {
return false;
}
@@ -5975,7 +5970,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
}
// keywords don't work outside battlefield
- if (!isInZone(ZoneType.Battlefield)) {
+ if (!isInPlay()) {
return true;
}
@@ -6324,7 +6319,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
public boolean isCommander() {
if (this.getMeldedWith() != null && this.getMeldedWith().isCommander())
return true;
- if (isInZone(ZoneType.Battlefield) && hasMergedCard()) {
+ if (isInPlay() && hasMergedCard()) {
for (final Card c : getMergedCards())
if (c.isCommander) return true;
}
@@ -6346,7 +6341,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
return this;
if (this.getMeldedWith() != null && this.getMeldedWith().isCommander())
return this.getMeldedWith();
- if (isInZone(ZoneType.Battlefield) && hasMergedCard()) {
+ if (isInPlay() && hasMergedCard()) {
for (final Card c : getMergedCards())
if (c.isCommander) return c;
}
diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java
index 824e9c9bf01..3f294a24501 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactory.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactory.java
@@ -816,7 +816,7 @@ public class CardFactory {
if (sa.hasParam("SetCreatureTypes")) {
// currently only Changeling and similar should be affected by that
// other cards using AddType$ ChosenType should not
- if (sta.hasParam("AddType") && CardType.AllCreatureTypes.equals(sta.getParam("AddType"))) {
+ if (sta.hasParam("AddAllCreatureTypes")) {
state.removeStaticAbility(sta);
}
}
diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
index fffd9b688bc..73103b937b7 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
@@ -38,6 +38,7 @@ import com.google.common.collect.Sets;
import forge.card.CardStateName;
import forge.card.CardType;
+import forge.card.CardTypeView;
import forge.card.ColorSet;
import forge.card.ICardFace;
import forge.card.MagicColor;
@@ -448,12 +449,14 @@ public class CardFactoryUtil {
final Map map = Maps.newHashMap();
for (final Card c : list) {
// Remove Duplicated types
- final Set creatureTypes = c.getType().getCreatureTypes();
- if (creatureTypes.contains(CardType.AllCreatureTypes)) {
+ CardTypeView type = c.getType();
+ if (type.hasAllCreatureTypes() && Iterables.isEmpty(type.getExcludedCreatureSubTypes())) {
allCreatureType++;
continue;
}
- for (String creatureType : creatureTypes) {
+ // if something has all creature types, but some are excluded, the count might be messed up
+
+ for (String creatureType : type.getCreatureTypes()) {
Integer count = map.get(creatureType);
map.put(creatureType, count == null ? 1 : count + 1);
}
@@ -477,16 +480,15 @@ public class CardFactoryUtil {
* a {@link forge.game.card.CardCollection} object.
* @return a string.
*/
- public static String[] getMostProminentCreatureType(final CardCollectionView list) {
+ public static Iterable getMostProminentCreatureType(final CardCollectionView list) {
if (list.isEmpty()) {
- return null;
+ return ImmutableList.of();
}
final Map map = Maps.newHashMap();
for (final Card c : list) {
// Remove Duplicated types
- final Set creatureTypes = c.getType().getCreatureTypes();
- for (String creatureType : creatureTypes) {
+ for (String creatureType : c.getType().getCreatureTypes()) {
Integer count = map.get(creatureType);
map.put(creatureType, count == null ? 1 : count + 1);
}
@@ -499,16 +501,16 @@ public class CardFactoryUtil {
}
}
if (max == 0) {
- return null;
+ return ImmutableList.of();
}
- StringBuilder sb = new StringBuilder();
+ List result = Lists.newArrayList();
for (final Entry entry : map.entrySet()) {
if (max == entry.getValue()) {
- sb.append(entry.getKey()).append(",");
+ result.add(entry.getKey());
}
}
- return sb.toString().split(",");
+ return result;
}
/**
@@ -718,8 +720,7 @@ public class CardFactoryUtil {
return validSource;
}
- public static ReplacementEffect makeEtbCounter(final String kw, final CardState card, final boolean intrinsic)
- {
+ public static ReplacementEffect makeEtbCounter(final String kw, final CardState card, final boolean intrinsic) {
String parse = kw;
String[] splitkw = parse.split(":");
@@ -1859,7 +1860,7 @@ public class CardFactoryUtil {
inst.addTrigger(parsedPlayTrigger);
} else if (keyword.equals("Training")) {
final String trigStr = "Mode$ Attacks | ValidCard$ Card.Self | Secondary$ True | " +
- "IsPresent$ Creature.attacking+Other+powerGTX | TriggerDescription$ Training (" +
+ "IsPresent$ Creature.attacking+Other+powerGTX | NoResolvingCheck$ True | TriggerDescription$ Training (" +
inst.getReminderText() + ")";
final String effect = "DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | Defined$ Self | Training$ True";
@@ -1871,7 +1872,6 @@ public class CardFactoryUtil {
trigger.setOverridingAbility(sa);
inst.addTrigger(trigger);
-
} else if (keyword.startsWith("Tribute")) {
// use hardcoded ability name
final String abStr = "TrigNotTribute";
@@ -3422,7 +3422,7 @@ public class CardFactoryUtil {
svars.put("AffinityX", "Count$Valid " + t + ".YouCtrl");
} else if (keyword.equals("Changeling")) {
effect = "Mode$ Continuous | EffectZone$ All | Affected$ Card.Self" +
- " | CharacteristicDefining$ True | AddType$ AllCreatureTypes | Secondary$ True" +
+ " | CharacteristicDefining$ True | AddAllCreatureTypes$ True | Secondary$ True" +
" | Description$ Changeling (" + inst.getReminderText() + ")";
} else if (keyword.equals("Cipher")) {
StringBuilder sb = new StringBuilder();
@@ -3599,7 +3599,7 @@ public class CardFactoryUtil {
}
altCostSA.setRestrictions(restriction);
- String costDescription = TextUtil.fastReplace(params.get("Description"),"CARDNAME", card.getName());
+ String costDescription = TextUtil.fastReplace(params.get("Description"), "CARDNAME", card.getName());
if (costDescription == null || costDescription.isEmpty()) {
costDescription = TextUtil.concatWithSpace("You may", abCost.toStringAlt(), "rather than pay", TextUtil.addSuffix(card.getName(), "'s mana cost."));
}
diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java
index 9063f5291ad..ba3ab337c9f 100644
--- a/forge-game/src/main/java/forge/game/card/CardProperty.java
+++ b/forge-game/src/main/java/forge/game/card/CardProperty.java
@@ -755,12 +755,9 @@ public class CardProperty {
}
} else if (property.startsWith("MostProminentCreatureTypeInLibrary")) {
final CardCollectionView list = sourceController.getCardsIn(ZoneType.Library);
- String[] type = CardFactoryUtil.getMostProminentCreatureType(list);
- if (type != null) {
- for (String s : type) {
- if (!card.getType().hasCreatureType(s)) {
- return false;
- }
+ for (String s : CardFactoryUtil.getMostProminentCreatureType(list)) {
+ if (!card.getType().hasCreatureType(s)) {
+ return false;
}
}
} else if (property.startsWith("sharesCreatureTypeWith")) {
diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java
index a23e834f198..5cced7cc806 100644
--- a/forge-game/src/main/java/forge/game/card/CardView.java
+++ b/forge-game/src/main/java/forge/game/card/CardView.java
@@ -874,7 +874,7 @@ public class CardView extends GameEntityView {
set(TrackableProperty.Modal, c.isModal());
//backside
- if (c.getAlternateState()!=null)
+ if (c.getAlternateState() != null)
updateBackSide(c.getAlternateState().getName(), c.hasBackSide());
final Card cloner = c.getCloner();
diff --git a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java
index 32c977ce5a2..4e764b3663f 100644
--- a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java
+++ b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java
@@ -185,11 +185,9 @@ public class TokenInfo {
}
}
if (!typeMap.isEmpty()) {
- String oldName = result.getName();
CardType type = new CardType(result.getType());
- String joinedName = StringUtils.join(type.getSubtypes(), " ");
- final boolean nameGenerated = oldName.equals(joinedName);
+ final boolean nameGenerated = result.getName().endsWith(" Token");
boolean typeChanged = false;
if (!Iterables.isEmpty(type.getSubtypes())) {
@@ -207,7 +205,7 @@ public class TokenInfo {
// update generated Name
if (nameGenerated) {
- result.setName(StringUtils.join(type.getSubtypes(), " "));
+ result.setName(StringUtils.join(type.getSubtypes(), " ") + " Token");
}
}
}
diff --git a/forge-game/src/main/java/forge/game/combat/AttackConstraints.java b/forge-game/src/main/java/forge/game/combat/AttackConstraints.java
index ef6499e1c81..2c1d89da64e 100644
--- a/forge-game/src/main/java/forge/game/combat/AttackConstraints.java
+++ b/forge-game/src/main/java/forge/game/combat/AttackConstraints.java
@@ -47,7 +47,7 @@ public class AttackConstraints {
public AttackConstraints(final Combat combat) {
final Game game = combat.getAttackingPlayer().getGame();
- possibleAttackers = CardLists.filter(combat.getAttackingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
+ possibleAttackers = combat.getAttackingPlayer().getCreaturesInPlay();
possibleDefenders = combat.getDefenders();
globalRestrictions = GlobalAttackRestrictions.getGlobalRestrictions(combat.getAttackingPlayer(), possibleDefenders);
diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java
index ea49ca6afd5..d32139d49f3 100644
--- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java
+++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java
@@ -1036,7 +1036,7 @@ public class CombatUtil {
* @return a boolean.
*/
public static boolean canBlock(final Card attacker, final Card blocker, final boolean nextTurn) {
- if ((attacker == null) || (blocker == null)) {
+ if (attacker == null || blocker == null) {
return false;
}
diff --git a/forge-game/src/main/java/forge/game/cost/CostReveal.java b/forge-game/src/main/java/forge/game/cost/CostReveal.java
index 470cec17b42..909e07573f7 100644
--- a/forge-game/src/main/java/forge/game/cost/CostReveal.java
+++ b/forge-game/src/main/java/forge/game/cost/CostReveal.java
@@ -77,7 +77,6 @@ public class CostReveal extends CostPartWithList {
return handList.size();
}
-
@Override
public final boolean canPay(final SpellAbility ability, final Player payer) {
final Card source = ability.getHostCard();
diff --git a/forge-game/src/main/java/forge/game/mana/Mana.java b/forge-game/src/main/java/forge/game/mana/Mana.java
index 92f061eb248..5f7b90d2d46 100644
--- a/forge-game/src/main/java/forge/game/mana/Mana.java
+++ b/forge-game/src/main/java/forge/game/mana/Mana.java
@@ -23,7 +23,6 @@ import forge.game.card.Card;
import forge.game.card.CardUtil;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility;
-import forge.game.zone.ZoneType;
/**
*
@@ -72,7 +71,7 @@ public class Mana {
public Mana(final byte color, final Card source, final AbilityManaPart manaAbility) {
this.color = color;
this.manaAbility = manaAbility;
- this.sourceCard = source.isInZone(ZoneType.Battlefield) ? CardUtil.getLKICopy(source) : source.getGame().getChangeZoneLKIInfo(source);
+ this.sourceCard = source.isInPlay() ? CardUtil.getLKICopy(source) : source.getGame().getChangeZoneLKIInfo(source);
}
@Override
diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java
index 2f890d8c5d1..3fe18c50722 100644
--- a/forge-game/src/main/java/forge/game/mana/ManaPool.java
+++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java
@@ -6,21 +6,21 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package forge.game.mana;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import org.apache.commons.lang3.StringUtils;
@@ -28,24 +28,28 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
-import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCostShard;
+import forge.game.Game;
import forge.game.GlobalRuleChange;
+import forge.game.ability.AbilityKey;
import forge.game.event.EventValueChangeType;
import forge.game.event.GameEventManaPool;
import forge.game.event.GameEventZone;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
+import forge.game.replacement.ReplacementLayer;
+import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility;
+import forge.game.staticability.StaticAbilityUnspentMana;
import forge.game.zone.ZoneType;
/**
*
* ManaPool class.
*
- *
+ *
* @author Forge
* @version $Id$
*/
@@ -64,9 +68,14 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
}
public void addMana(final Mana mana) {
+ addMana(mana, true);
+ }
+ public void addMana(final Mana mana, boolean updateView) {
floatingMana.put(mana.getColor(), mana);
- owner.updateManaForView();
- owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Added, mana));
+ if (updateView) {
+ owner.updateManaForView();
+ owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Added, mana));
+ }
}
public final void add(final Iterable manaList) {
@@ -78,47 +87,55 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
/**
*
* willManaBeLostAtEndOfPhase.
- *
+ *
* @return - whether floating mana will be lost if the current phase ended right now
*
*/
public final boolean willManaBeLostAtEndOfPhase() {
- if (floatingMana.isEmpty() ||
- owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty) ||
- owner.hasKeyword("Convert unused mana to Colorless")) {
+ if (floatingMana.isEmpty()) {
+ return false;
+ }
+
+ final Map runParams = AbilityKey.mapFromAffected(owner);
+ if (!owner.getGame().getReplacementHandler().getReplacementList(ReplacementType.LoseMana, runParams, ReplacementLayer.Other).isEmpty()) {
return false;
}
int safeMana = 0;
- for (final byte c : MagicColor.WUBRG) {
- final String captName = StringUtils.capitalize(MagicColor.toLongString(c));
- if (owner.hasKeyword(captName + " mana doesn't empty from your mana pool as steps and phases end.")) {
- safeMana += getAmountOfColor(c);
- }
+ for (final byte c : StaticAbilityUnspentMana.getManaToKeep(owner)) {
+ safeMana += getAmountOfColor(c);
}
return totalMana() != safeMana; //won't lose floating mana if all mana is of colors that aren't going to be emptied
}
+ public final boolean hasBurn() {
+ final Game game = owner.getGame();
+ return game.getRules().hasManaBurn() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn);
+ }
+
public final List clearPool(boolean isEndOfPhase) {
// isEndOfPhase parameter: true = end of phase, false = mana drain effect
- List cleared = new ArrayList<>();
+ List cleared = Lists.newArrayList();
if (floatingMana.isEmpty()) { return cleared; }
- if (isEndOfPhase && owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty)) {
- return cleared;
- }
+ boolean convertToColorless = false;
- final boolean convertToColorless = owner.hasKeyword("Convert unused mana to Colorless");
+ final Map runParams = AbilityKey.mapFromAffected(owner);
+ switch (owner.getGame().getReplacementHandler().run(ReplacementType.LoseMana, runParams)) {
+ case NotReplaced:
+ break;
+ case Skipped:
+ return cleared;
+ default: // the only ones that does replace losing Mana are making it colorless instead
+ convertToColorless = true;
+ break;
+
+ }
final List keys = Lists.newArrayList(floatingMana.keySet());
if (isEndOfPhase) {
- for (final Byte c : Lists.newArrayList(keys)) {
- final String captName = StringUtils.capitalize(MagicColor.toLongString(c));
- if (owner.hasKeyword(captName + " mana doesn't empty from your mana pool as steps and phases end.")) {
- keys.remove(c);
- }
- }
+ keys.removeAll(StaticAbilityUnspentMana.getManaToKeep(owner));
}
if (convertToColorless) {
keys.remove(Byte.valueOf((byte)ManaAtom.COLORLESS));
@@ -126,29 +143,22 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
for (Byte b : keys) {
Collection cm = floatingMana.get(b);
+ final List pMana = Lists.newArrayList();
if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) {
- final List pMana = new ArrayList<>();
for (final Mana mana : cm) {
if (mana.getManaAbility()!= null && mana.getManaAbility().isPersistentMana()) {
pMana.add(mana);
}
}
- cm.removeAll(pMana);
- if (convertToColorless) {
- convertManaColor(b, (byte)ManaAtom.COLORLESS);
- cm.addAll(pMana);
- } else {
- cleared.addAll(cm);
- cm.clear();
- floatingMana.putAll(b, pMana);
- }
+ }
+ cm.removeAll(pMana);
+ if (convertToColorless) {
+ convertManaColor(b, (byte)ManaAtom.COLORLESS);
+ cm.addAll(pMana);
} else {
- if (convertToColorless) {
- convertManaColor(b, (byte)ManaAtom.COLORLESS);
- } else {
- cleared.addAll(cm);
- cm.clear();
- }
+ cleared.addAll(cm);
+ cm.clear();
+ floatingMana.putAll(b, pMana);
}
}
@@ -158,7 +168,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
}
private void convertManaColor(final byte originalColor, final byte toColor) {
- List convert = new ArrayList<>();
+ List convert = Lists.newArrayList();
Collection cm = floatingMana.get(originalColor);
for (Mana m : cm) {
convert.add(new Mana(toColor, m.getSourceCard(), m.getManaAbility()));
@@ -168,8 +178,11 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
owner.updateManaForView();
}
- private boolean removeMana(final Mana mana) {
- if (floatingMana.remove(mana.getColor(), mana)) {
+ public boolean removeMana(final Mana mana) {
+ return removeMana(mana, true);
+ }
+ public boolean removeMana(final Mana mana, boolean updateView) {
+ if (floatingMana.remove(mana.getColor(), mana) && updateView) {
owner.updateManaForView();
owner.getGame().fireEvent(new GameEventManaPool(owner, EventValueChangeType.Removed, mana));
return true;
@@ -249,7 +262,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
return false;
}
- final List removeFloating = new ArrayList<>();
+ final List removeFloating = Lists.newArrayList();
boolean manaNotAccountedFor = false;
// loop over mana produced by mana ability
@@ -333,4 +346,5 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
public Iterator iterator() {
return floatingMana.values().iterator();
}
+
}
diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
index aa976ed0cb9..20c795e9fa8 100644
--- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
+++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
@@ -309,6 +309,7 @@ public class PhaseHandler implements java.io.Serializable {
break;
case COMBAT_BEGIN:
+ nCombatsThisTurn++;
combat = new Combat(playerTurn);
//PhaseUtil.verifyCombat();
break;
@@ -478,9 +479,7 @@ public class PhaseHandler implements java.io.Serializable {
for (Player p : game.getPlayers()) {
int burn = p.getManaPool().clearPool(true).size();
- boolean manaBurns = game.getRules().hasManaBurn() ||
- (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn));
- if (manaBurns) {
+ if (p.getManaPool().hasBurn()) {
p.loseLife(burn, false, true);
}
}
@@ -605,7 +604,6 @@ public class PhaseHandler implements java.io.Serializable {
return;
}
- nCombatsThisTurn++;
// Reset all active Triggers
game.getTriggerHandler().resetActiveTriggers();
@@ -981,15 +979,18 @@ public class PhaseHandler implements java.io.Serializable {
}
public final boolean isFirstCombat() {
- return (nCombatsThisTurn == 1);
+ return nCombatsThisTurn == 1;
+ }
+ public final int getNumCombat() {
+ return nCombatsThisTurn;
}
public final boolean isFirstUpkeep() {
- return is(PhaseType.UPKEEP) && (nUpkeepsThisTurn == 0);
+ return is(PhaseType.UPKEEP) && nUpkeepsThisTurn == 0;
}
public final boolean isFirstUpkeepThisGame() {
- return is(PhaseType.UPKEEP) && (nUpkeepsThisGame == 0);
+ return is(PhaseType.UPKEEP) && nUpkeepsThisGame == 0;
}
public final boolean isPreCombatMain() {
@@ -998,7 +999,7 @@ public class PhaseHandler implements java.io.Serializable {
}
public final boolean beforeFirstPostCombatMainEnd() {
- return (nMain2sThisTurn == 0);
+ return nMain2sThisTurn == 0;
}
private final static boolean DEBUG_PHASES = false;
@@ -1164,11 +1165,17 @@ public class PhaseHandler implements java.io.Serializable {
}
public final boolean devAdvanceToPhase(PhaseType targetPhase) {
+ return devAdvanceToPhase(targetPhase, null);
+ }
+ public final boolean devAdvanceToPhase(PhaseType targetPhase, Runnable resolver) {
boolean isTopsy = playerTurn.getAmountOfKeyword("The phases of your turn are reversed.") % 2 == 1;
while (phase.isBefore(targetPhase, isTopsy)) {
if (checkStateBasedEffects()) {
return false;
}
+ if (resolver != null) {
+ resolver.run();
+ }
onPhaseEnd();
advanceToNextPhase();
onPhaseBegin();
diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java
index 6ad07f3c382..8f89b8e00a8 100644
--- a/forge-game/src/main/java/forge/game/player/Player.java
+++ b/forge-game/src/main/java/forge/game/player/Player.java
@@ -105,6 +105,7 @@ import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityCantBeCast;
import forge.game.staticability.StaticAbilityCantDraw;
+import forge.game.staticability.StaticAbilityCantGainLosePayLife;
import forge.game.staticability.StaticAbilityCantPutCounter;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
@@ -539,7 +540,7 @@ public class Player extends GameEntity implements Comparable {
}
public final boolean canGainLife() {
- return !hasLost() && !hasKeyword("You can't gain life.") && !hasKeyword("Your life total can't change.");
+ return !hasLost() && !StaticAbilityCantGainLosePayLife.anyCantGainLife(this);
}
public final int loseLife(int toLose, final boolean damage, final boolean manaBurn) {
@@ -607,14 +608,14 @@ public class Player extends GameEntity implements Comparable {
}
public final boolean canLoseLife() {
- return !hasLost() && !hasKeyword("Your life total can't change.");
+ return !hasLost() && !StaticAbilityCantGainLosePayLife.anyCantLosePayLife(this);
}
public final boolean canPayLife(final int lifePayment) {
if (lifePayment > 0 && life < lifePayment) {
return false;
}
- return (lifePayment <= 0) || !hasKeyword("Your life total can't change.");
+ return (lifePayment <= 0) || !StaticAbilityCantGainLosePayLife.anyCantLosePayLife(this);
}
public final boolean payLife(final int lifePayment, final Card source) {
@@ -2146,9 +2147,6 @@ public class Player extends GameEntity implements Comparable {
}
public final boolean hasProwl(final String type) {
- if (prowl.contains(CardType.AllCreatureTypes)) {
- return true;
- }
return prowl.contains(type);
}
public final void addProwlType(final String type) {
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceLoseMana.java b/forge-game/src/main/java/forge/game/replacement/ReplaceLoseMana.java
new file mode 100644
index 00000000000..8b888be7462
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/replacement/ReplaceLoseMana.java
@@ -0,0 +1,52 @@
+/*
+ * Forge: Play Magic: the Gathering.
+ * Copyright (C) 2011 Forge Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package forge.game.replacement;
+
+import java.util.Map;
+
+import forge.game.ability.AbilityKey;
+import forge.game.card.Card;
+import forge.game.spellability.SpellAbility;
+
+public class ReplaceLoseMana extends ReplacementEffect {
+
+ public ReplaceLoseMana(Map map, Card host, boolean intrinsic) {
+ super(map, host, intrinsic);
+ }
+
+ /* (non-Javadoc)
+ * @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
+ */
+ @Override
+ public boolean canReplace(Map runParams) {
+ if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
+ */
+ @Override
+ public void setReplacingObjects(Map runParams, SpellAbility sa) {
+ sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
+ }
+
+}
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java
index a0e2011a456..e71dcb07162 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java
@@ -223,8 +223,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
String currentName;
if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
currentName = cardState.getName();
- }
- else {
+ } else {
currentName = getHostCard().getName();
}
desc = CardTranslation.translateSingleDescriptionText(desc, currentName);
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
index 10187567ef5..7d8eed43a24 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
@@ -278,9 +278,6 @@ public class ReplacementHandler {
// Log there
String message = chosenRE.getDescription();
if (!StringUtils.isEmpty(message)) {
- if (chosenRE.getHostCard() != null) {
- message = TextUtil.fastReplace(message, "CARDNAME", chosenRE.getHostCard().getName());
- }
game.getGameLog().add(GameLogEntryType.EFFECT_REPLACED, message);
}
@@ -365,17 +362,17 @@ public class ReplacementHandler {
// Decider gets to choose whether or not to apply the replacement.
if (replacementEffect.hasParam("Optional")) {
Player optDecider = decider;
- if (replacementEffect.hasParam("OptionalDecider") && (effectSA != null)) {
+ if (replacementEffect.hasParam("OptionalDecider") && effectSA != null) {
effectSA.setActivatingPlayer(host.getController());
optDecider = AbilityUtils.getDefinedPlayers(host,
replacementEffect.getParam("OptionalDecider"), effectSA).get(0);
}
- Card cardForUi = host.getCardForUi();
- String effectDesc = TextUtil.fastReplace(replacementEffect.getDescription(), "CARDNAME", CardTranslation.getTranslatedName(cardForUi.getName()));
+ String name = CardTranslation.getTranslatedName(host.getCardForUi().getName());
+ String effectDesc = TextUtil.fastReplace(replacementEffect.getDescription(), "CARDNAME", name);
final String question = replacementEffect instanceof ReplaceDiscard
- ? Localizer.getInstance().getMessage("lblApplyCardReplacementEffectToCardConfirm", CardTranslation.getTranslatedName(cardForUi.getName()), runParams.get(AbilityKey.Card).toString(), effectDesc)
- : Localizer.getInstance().getMessage("lblApplyReplacementEffectOfCardConfirm", CardTranslation.getTranslatedName(cardForUi.getName()), effectDesc);
+ ? Localizer.getInstance().getMessage("lblApplyCardReplacementEffectToCardConfirm", name, runParams.get(AbilityKey.Card).toString(), effectDesc)
+ : Localizer.getInstance().getMessage("lblApplyReplacementEffectOfCardConfirm", name, effectDesc);
GameEntity affected = (GameEntity) runParams.get(AbilityKey.Affected);
boolean confirmed = optDecider.getController().confirmReplacementEffect(replacementEffect, effectSA, affected, question);
if (!confirmed) {
@@ -403,7 +400,6 @@ public class ReplacementHandler {
if ("True".equals(replacementEffect.getParam("Skip"))) {
return ReplacementResult.Skipped; // Event is skipped.
}
-
Player player = host.getController();
if (effectSA != null) {
@@ -422,6 +418,10 @@ public class ReplacementHandler {
}
}
+ if ("Replaced".equals(replacementEffect.getParam("ReplacementResult"))) {
+ return ReplacementResult.Replaced; // Event is replaced without SA.
+ }
+
// if the spellability is a replace effect then its some new logic
// if ReplacementResult is set in run params use that instead
if (runParams.containsKey(AbilityKey.ReplacementResult)) {
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java b/forge-game/src/main/java/forge/game/replacement/ReplacementType.java
index b63f9377961..2c9b8cc3700 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementType.java
@@ -30,6 +30,7 @@ public enum ReplacementType {
GameLoss(ReplaceGameLoss.class),
Learn(ReplaceLearn.class),
LifeReduced(ReplaceLifeReduced.class),
+ LoseMana(ReplaceLoseMana.class),
Mill(ReplaceMill.class),
Moved(ReplaceMoved.class),
ProduceMana(ReplaceProduceMana.class),
diff --git a/forge-game/src/main/java/forge/game/spellability/LandAbility.java b/forge-game/src/main/java/forge/game/spellability/LandAbility.java
index f02d2143c47..04f2c3e60ce 100644
--- a/forge-game/src/main/java/forge/game/spellability/LandAbility.java
+++ b/forge-game/src/main/java/forge/game/spellability/LandAbility.java
@@ -26,7 +26,6 @@ import forge.game.card.CardUtil;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.staticability.StaticAbility;
-import forge.game.zone.ZoneType;
public class LandAbility extends Ability {
@@ -78,7 +77,7 @@ public class LandAbility extends Ability {
getMayPlay().incMayPlayTurn();
}
// if land isn't in battlefield try to reset the card state
- if (result != null && !result.isInZone(ZoneType.Battlefield)) {
+ if (result != null && !result.isInPlay()) {
result.setState(CardStateName.Original, true);
}
}
diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
index 453f28847f4..ece876bceff 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
@@ -59,10 +59,12 @@ import forge.game.card.CardPredicates;
import forge.game.card.CardZoneTable;
import forge.game.cost.Cost;
import forge.game.cost.CostPart;
+import forge.game.cost.CostTap;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.keyword.Keyword;
import forge.game.mana.Mana;
import forge.game.mana.ManaCostBeingPaid;
+import forge.game.phase.Untap;
import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.replacement.ReplacementEffect;
@@ -2204,8 +2206,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
int score = 0;
if (manaPart == null) {
score++; //Assume a mana ability can generate at least 1 mana if the amount of mana can't be determined now.
- }
- else {
+ } else {
String mana = manaPart.mana();
if (!mana.equals("Any")) {
score += mana.length();
@@ -2213,8 +2214,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
// Producing colorless should produce a slightly lower score
score += 1;
}
- }
- else {
+ } else {
score += 7;
}
}
@@ -2227,6 +2227,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (!costPart.isRenewable()) {
score += 3;
}
+ if (costPart instanceof CostTap && !Untap.canUntap(getHostCard())) {
+ score += 10;
+ }
// Increase score by 1 for each costpart in general
score++;
}
diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java
index 8b4966d853f..ca779eefe7a 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java
@@ -134,6 +134,10 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
this.setPhases(PhaseType.parseRange(params.get("ActivationPhases")));
}
+ if (params.containsKey("ActivationFirstCombat")) {
+ this.setFirstCombatOnly(true);
+ }
+
if (params.containsKey("ActivationGameTypes")) {
this.setGameTypes(GameType.listValueOf(params.get("ActivationGameTypes")));
}
@@ -207,7 +211,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
// for Bestow need to check the animated State
if (sa.isSpell() && sa.isBestow()) {
// already bestowed or in battlefield, no need to check for spell
- if (c.isInZone(ZoneType.Battlefield)) {
+ if (c.isInPlay()) {
return false;
}
@@ -319,6 +323,12 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
return false;
}
}
+
+ if (this.getFirstCombatOnly()) {
+ if (game.getPhaseHandler().getNumCombat() > 1) {
+ return false;
+ }
+ }
return true;
}
@@ -480,7 +490,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
// 702.36e
// If the permanent wouldn’t have a morph cost if it were face up, it can’t be turned face up this way.
- if (sa.isMorphUp() && c.isInZone(ZoneType.Battlefield)) {
+ if (sa.isMorphUp() && c.isInPlay()) {
Card cp = c;
if (!c.isLKI()) {
cp = CardUtil.getLKICopy(c);
diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java
index c8b2d3215f1..814eecda2fb 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java
@@ -58,6 +58,7 @@ public class SpellAbilityVariables implements Cloneable {
public void setVariables(SpellAbilityVariables sav) {
this.zone = sav.getZone();
this.phases = Sets.newEnumSet(sav.getPhases(), PhaseType.class);
+ this.firstCombatOnly = sav.getFirstCombatOnly();
this.gameTypes = Sets.newEnumSet(sav.getGameTypes(), GameType.class);
this.sorcerySpeed = sav.isSorcerySpeed();
this.instantSpeed = sav.isInstantSpeed();
@@ -104,6 +105,8 @@ public class SpellAbilityVariables implements Cloneable {
/** The phases. */
private Set phases = EnumSet.noneOf(PhaseType.class);
+ private boolean firstCombatOnly = false;
+
/** The GameTypes */
private Set gameTypes = EnumSet.noneOf(GameType.class);
@@ -695,10 +698,22 @@ public class SpellAbilityVariables implements Cloneable {
return this.phases;
}
+ /**
+ * Gets the first combat.
+ *
+ * @return first combat
+ */
+ public final boolean getFirstCombatOnly() {
+ return this.firstCombatOnly;
+ }
+ public final boolean setFirstCombatOnly(boolean first) {
+ return this.firstCombatOnly = first;
+ }
+
/**
* Gets the game types.
*
- * @return the phases
+ * @return the game types
*/
public final Set getGameTypes() {
return this.gameTypes;
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java
index 778a5f27d49..78b2f9ce273 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java
@@ -148,6 +148,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
}
if (hasParam("AddType") || hasParam("RemoveType")
+ || hasParam("AddAllCreatureTypes")
|| hasParam("RemoveCardTypes") || hasParam("RemoveSubTypes")
|| hasParam("RemoveSuperTypes") || hasParam("RemoveLandTypes")
|| hasParam("RemoveCreatureTypes") || hasParam("RemoveArtifactTypes")
@@ -208,8 +209,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
String currentName;
if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
currentName = cardState.getName();
- }
- else {
+ } else {
currentName = getHostCard().getName();
}
String desc = CardTranslation.translateSingleDescriptionText(getParam("Description"), currentName);
@@ -432,7 +432,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
}
}
} else if (!hasParam("CharacteristicDefining")) {
- if (!getHostCard().isInZone(ZoneType.Battlefield)) { // default
+ if (!getHostCard().isInPlay()) { // default
return false;
}
}
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java
index e052d645a69..b1507945a95 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityAdapt.java
@@ -23,7 +23,6 @@ public class StaticAbilityAdapt {
}
return false;
}
-
public static boolean applyWithAdapt(final StaticAbility stAb, final SpellAbility sa, final Card card) {
if (!stAb.matchesValidParam("ValidCard", card)) {
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java
index f298d3ef624..a466761a774 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantBeCast.java
@@ -171,23 +171,6 @@ public class StaticAbilityCantBeCast {
return false;
}
- // TODO refactor this ones using ValidSA above
- if (stAb.hasParam("NonMana") && (spellAbility.isManaAbility())) {
- return false;
- }
-
- if (stAb.hasParam("NonLoyalty") && spellAbility.isPwAbility()) {
- return false;
- }
-
- if (stAb.hasParam("Loyalty") && !spellAbility.isPwAbility()) {
- return false;
- }
-
- if (stAb.hasParam("TapAbility") && !(spellAbility.getPayCosts().hasTapCost())) {
- return false;
- }
-
if (stAb.hasParam("NonActivatorTurn") && (activator != null)
&& activator.getGame().getPhaseHandler().isPlayerTurn(activator)) {
return false;
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantGainLosePayLife.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantGainLosePayLife.java
new file mode 100644
index 00000000000..59cfaeecf8d
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantGainLosePayLife.java
@@ -0,0 +1,54 @@
+package forge.game.staticability;
+
+import forge.game.Game;
+import forge.game.card.Card;
+import forge.game.player.Player;
+import forge.game.zone.ZoneType;
+
+public class StaticAbilityCantGainLosePayLife {
+
+ static String MODE_CANT_GAIN_LIFE = "CantGainLife";
+ static String MODE_CANT_CHANGE_LIFE = "CantChangeLife";
+
+ public static boolean anyCantGainLife(final Player player) {
+ final Game game = player.getGame();
+ for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
+ for (final StaticAbility stAb : ca.getStaticAbilities()) {
+ if (stAb.isSuppressed() || !stAb.checkConditions()) {
+ continue;
+ }
+ if (!(stAb.getParam("Mode").equals(MODE_CANT_GAIN_LIFE) || stAb.getParam("Mode").equals(MODE_CANT_CHANGE_LIFE))) {
+ continue;
+ }
+
+ if (applyCommonAbility(stAb, player)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static boolean anyCantLosePayLife(final Player player) {
+ final Game game = player.getGame();
+ for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
+ for (final StaticAbility stAb : ca.getStaticAbilities()) {
+ if (!stAb.getParam("Mode").equals(MODE_CANT_CHANGE_LIFE) || stAb.isSuppressed() || !stAb.checkConditions()) {
+ continue;
+ }
+
+ if (applyCommonAbility(stAb, player)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static boolean applyCommonAbility(final StaticAbility stAb, final Player player) {
+ if (!stAb.matchesValidParam("ValidPlayer", player)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java
index 2e67893bc55..fc0aa270f94 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTarget.java
@@ -61,17 +61,15 @@ public class StaticAbilityCantTarget {
return false;
}
} else { // default zone is battlefield
- if (!card.isInZone(ZoneType.Battlefield)) {
+ if (!card.isInPlay()) {
return false;
}
}
-
if (!stAb.matchesValidParam("ValidCard", card)) {
return false;
}
-
if (stAb.hasParam("Hexproof") && (activator != null)) {
for (KeywordInterface kw : activator.getKeywords()) {
String k = kw.getOriginal();
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
index af3e7614277..f9512018a0f 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
@@ -140,6 +140,7 @@ public final class StaticAbilityContinuous {
String[] addStatics = null;
boolean removeAllAbilities = false;
boolean removeNonMana = false;
+ boolean addAllCreatureTypes = false;
boolean removeSuperTypes = false;
boolean removeCardTypes = false;
boolean removeSubTypes = false;
@@ -426,6 +427,9 @@ public final class StaticAbilityContinuous {
}
});
}
+ if (params.containsKey("AddAllCreatureTypes")) {
+ addAllCreatureTypes = true;
+ }
if (params.containsKey("RemoveSuperTypes")) {
removeSuperTypes = true;
}
@@ -855,11 +859,7 @@ public final class StaticAbilityContinuous {
if (params.containsKey("TriggerRememberDefined")) {
String triggerRemembered = (params.get("TriggerRememberDefined"));
for (final String rem : triggerRemembered.split(",")) {
- for (final Object o : AbilityUtils.getDefinedObjects(hostCard, rem, stAb)) {
- if (o instanceof SpellAbility) {
- // "RememberObjects$ Remembered" don't remember spellability
- continue;
- }
+ for (final Object o : AbilityUtils.getDefinedEntities(hostCard, rem, stAb)) {
actualTrigger.addRemembered(o);
}
}
@@ -895,10 +895,11 @@ public final class StaticAbilityContinuous {
}
// add Types
- if ((addTypes != null) || (removeTypes != null)) {
- affectedCard.addChangedCardTypes(addTypes, removeTypes, removeSuperTypes, removeCardTypes,
- removeSubTypes, removeLandTypes, removeCreatureTypes, removeArtifactTypes,
- removeEnchantmentTypes, hostCard.getTimestamp(), stAb.getId(), true, stAb.hasParam("CharacteristicDefining"));
+ if ((addTypes != null) || (removeTypes != null) || addAllCreatureTypes
+ || removeSuperTypes || removeCardTypes || removeLandTypes || removeCreatureTypes || removeArtifactTypes || removeEnchantmentTypes) {
+ affectedCard.addChangedCardTypes(addTypes, removeTypes, addAllCreatureTypes, removeSuperTypes, removeCardTypes, removeSubTypes,
+ removeLandTypes, removeCreatureTypes, removeArtifactTypes, removeEnchantmentTypes,
+ hostCard.getTimestamp(), stAb.getId(), true, stAb.hasParam("CharacteristicDefining"));
}
// add colors
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityUnspentMana.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityUnspentMana.java
new file mode 100644
index 00000000000..f8896db7769
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityUnspentMana.java
@@ -0,0 +1,45 @@
+package forge.game.staticability;
+
+import java.util.Collection;
+import java.util.Set;
+
+import com.google.common.collect.Sets;
+
+import forge.card.MagicColor;
+import forge.card.mana.ManaAtom;
+import forge.game.Game;
+import forge.game.card.Card;
+import forge.game.player.Player;
+import forge.game.zone.ZoneType;
+
+public class StaticAbilityUnspentMana {
+
+ static String MODE = "UnspentMana";
+
+ public static Collection getManaToKeep(final Player player) {
+ final Game game = player.getGame();
+ Set result = Sets.newHashSet();
+ for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
+ for (final StaticAbility stAb : ca.getStaticAbilities()) {
+ if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) {
+ continue;
+ }
+ applyUnspentManaAbility(stAb, player, result);
+ }
+ }
+ return result;
+ }
+
+ public static void applyUnspentManaAbility(final StaticAbility stAb, final Player player, Set result) {
+ if (!stAb.matchesValidParam("ValidPlayer", player)) {
+ return;
+ }
+ if (!stAb.hasParam("ManaType")) {
+ for (byte b : ManaAtom.MANATYPES) {
+ result.add(b);
+ }
+ } else {
+ result.add(MagicColor.fromName(stAb.getParam("ManaType")));
+ }
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java
index 69f890be854..713da987d6a 100644
--- a/forge-game/src/main/java/forge/game/trigger/Trigger.java
+++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java
@@ -130,7 +130,6 @@ public abstract class Trigger extends TriggerReplacementBase {
public String toString(boolean active) {
if (hasParam("TriggerDescription") && !this.isSuppressed()) {
-
StringBuilder sb = new StringBuilder();
String currentName;
if (this.isIntrinsic() && cardState != null && cardState.getCard() == getHostCard()) {
diff --git a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java
index 1460f9f175e..277fd56868c 100644
--- a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java
+++ b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java
@@ -221,7 +221,7 @@ public class WrappedAbility extends Ability {
public String toUnsuppressedString() {
String desc = this.getStackDescription(); /* use augmented stack description as string for wrapped things */
String card = getHostCard().toString();
- if ( !desc.contains(card) && desc.contains(" this ")) { /* a hack for Evolve and similar that don't have CARDNAME */
+ if (!desc.contains(card) && desc.contains(" this ")) { /* a hack for Evolve and similar that don't have CARDNAME */
return card + ": " + desc;
} else return desc;
}
diff --git a/forge-game/src/main/java/forge/game/zone/PlayerZone.java b/forge-game/src/main/java/forge/game/zone/PlayerZone.java
index 160042f0bed..ab52d23fee0 100644
--- a/forge-game/src/main/java/forge/game/zone/PlayerZone.java
+++ b/forge-game/src/main/java/forge/game/zone/PlayerZone.java
@@ -56,7 +56,7 @@ public class PlayerZone extends Zone {
return true;
}
- if (c.isLand() && (!c.mayPlay(c.getController()).isEmpty())) {
+ if (c.isLand() && !c.mayPlay(c.getController()).isEmpty()) {
return true;
}
diff --git a/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java b/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java
index 8f6890555b5..8d361f92fe6 100644
--- a/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java
+++ b/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java
@@ -52,7 +52,6 @@ public class PlayerZoneBattlefield extends PlayerZone {
meldedCards.remove(c);
}
-
/** {@inheritDoc} */
@Override
public final void add(final Card c, final Integer position, final Card latestState) {
diff --git a/forge-game/src/main/java/forge/game/zone/Zone.java b/forge-game/src/main/java/forge/game/zone/Zone.java
index b7446cb7d15..a380f6b8b1a 100644
--- a/forge-game/src/main/java/forge/game/zone/Zone.java
+++ b/forge-game/src/main/java/forge/game/zone/Zone.java
@@ -81,11 +81,9 @@ public class Zone implements java.io.Serializable, Iterable {
public final void add(final Card c) {
add(c, null);
}
-
public final void add(final Card c, final Integer index) {
add(c, index, null);
}
-
public void add(final Card c, Integer index, final Card latestState) {
if (index != null && cardList.isEmpty() && index.intValue() > 0) {
// something went wrong, most likely the method fired when the game was in an unexpected state
diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java
index cc8903b14f9..0aaa4ba989c 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java
@@ -11,11 +11,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Vector;
-import javax.swing.JButton;
-import javax.swing.JPanel;
-import javax.swing.ListSelectionModel;
-import javax.swing.ScrollPaneConstants;
-import javax.swing.SwingConstants;
+import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
@@ -84,6 +80,8 @@ public class VLobby implements ILobbyView {
private final StartButton btnStart = new StartButton();
private final JPanel pnlStart = new JPanel(new MigLayout("insets 0, gap 0, wrap 2"));
+ private final JComboBox gamesInMatch = new JComboBox(new String[] {"1","3","5"});
+ private final JPanel gamesInMatchFrame = new JPanel(new MigLayout("insets 0, gap 0, wrap 2"));
private final JPanel constructedFrame = new JPanel(new MigLayout("insets 0, gap 0, wrap 2")); // Main content frame
// Variants frame and variables
@@ -212,11 +210,19 @@ public class VLobby implements ILobbyView {
public final void actionPerformed(final ActionEvent arg0) {
Runnable startGame = lobby.startGame();
if (startGame != null) {
+ if (!gamesInMatch.getSelectedItem().equals(ForgePreferences.FPref.UI_MATCHES_PER_GAME)) {
+ FModel.getPreferences().setPref(FPref.UI_MATCHES_PER_GAME, (String) gamesInMatch.getSelectedItem());
+ }
startGame.run();
}
}
});
}
+ gamesInMatchFrame.add(newLabel(localizer.getMessage("lblGamesInMatch")), "w 150px!, h 30px!");
+ gamesInMatchFrame.add(gamesInMatch, "w 50px!, h 30px!");
+ gamesInMatchFrame.setOpaque(false);
+ gamesInMatch.setSelectedItem("3");
+ pnlStart.add(gamesInMatchFrame);
}
public void updateDeckPanel() {
diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java
index 54d7c376fba..86a63f6ceee 100644
--- a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java
+++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulatorTest.java
@@ -48,7 +48,7 @@ public class GameSimulatorTest extends SimulationTestCase {
assertEquals(1, heraldCopy.getToughnessBonusFromCounters());
assertEquals(1, heraldCopy.getPowerBonusFromCounters());
- Card warriorToken = findCardWithName(simGame, "Warrior");
+ Card warriorToken = findCardWithName(simGame, "Warrior Token");
assertNotNull(warriorToken);
assertTrue(warriorToken.isSick());
assertEquals(1, warriorToken.getCurrentPower());
@@ -233,7 +233,7 @@ public class GameSimulatorTest extends SimulationTestCase {
GameSimulator sim = createSimulator(game, p);
sim.simulateSpellAbility(minusTwo);
Game simGame = sim.getSimulatedGameState();
- Card vampireToken = findCardWithName(simGame, "Vampire");
+ Card vampireToken = findCardWithName(simGame, "Vampire Token");
assertNotNull(vampireToken);
Player simP = simGame.getPlayers().get(1);
@@ -599,7 +599,7 @@ public class GameSimulatorTest extends SimulationTestCase {
assertTrue(score > 0);
Game simGame = sim.getSimulatedGameState();
- Card scion = findCardWithName(simGame, "Eldrazi Scion");
+ Card scion = findCardWithName(simGame, "Eldrazi Scion Token");
assertNotNull(scion);
assertEquals(1, scion.getNetPower());
assertEquals(1, scion.getNetToughness());
@@ -608,7 +608,7 @@ public class GameSimulatorTest extends SimulationTestCase {
GameCopier copier = new GameCopier(simGame);
Game copy = copier.makeCopy();
- Card scionCopy = findCardWithName(copy, "Eldrazi Scion");
+ Card scionCopy = findCardWithName(copy, "Eldrazi Scion Token");
assertNotNull(scionCopy);
assertEquals(1, scionCopy.getNetPower());
assertEquals(1, scionCopy.getNetToughness());
@@ -1288,7 +1288,7 @@ public class GameSimulatorTest extends SimulationTestCase {
assertTrue(score > 0);
Game simGame = sim.getSimulatedGameState();
- int numZombies = countCardsWithName(simGame, "Zombie");
+ int numZombies = countCardsWithName(simGame, "Zombie Token");
assertEquals(2, numZombies);
}
@@ -1323,11 +1323,11 @@ public class GameSimulatorTest extends SimulationTestCase {
GameSimulator sim = createSimulator(game, p);
int score = sim.simulateSpellAbility(fatalPushSA).value;
assertTrue(score > 0);
- assertEquals(2, countCardsWithName(sim.getSimulatedGameState(), "Zombie"));
+ assertEquals(2, countCardsWithName(sim.getSimulatedGameState(), "Zombie Token"));
score = sim.simulateSpellAbility(electrifySA).value;
assertTrue(score > 0);
- assertEquals(countCardsWithName(sim.getSimulatedGameState(), "Zombie"), 4);
+ assertEquals(4, countCardsWithName(sim.getSimulatedGameState(), "Zombie Token"));
}
public void testPlayerXCount() {
@@ -1564,7 +1564,7 @@ public class GameSimulatorTest extends SimulationTestCase {
assertTrue(score > 0);
Game simGame = sim.getSimulatedGameState();
- int numZombies = countCardsWithName(simGame, "Zombie");
+ int numZombies = countCardsWithName(simGame, "Zombie Token");
assertEquals(4, numZombies);
}
@@ -1592,7 +1592,7 @@ public class GameSimulatorTest extends SimulationTestCase {
assertTrue(score > 0);
Game simGame = sim.getSimulatedGameState();
- int numZombies = countCardsWithName(simGame, "Zombie");
+ int numZombies = countCardsWithName(simGame, "Zombie Token");
assertEquals(3, numZombies);
}
diff --git a/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java b/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java
index 503e5ba46b7..6c0cf5d013c 100644
--- a/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java
+++ b/forge-gui-desktop/src/test/java/forge/card/CardDbTestCase.java
@@ -2101,7 +2101,7 @@ public class CardDbTestCase extends ForgeCardMockTestCase {
@Test
public void testMaxArtCountForBasicLand(){
int maxArtIndex = this.cardDb.getMaxArtIndex("Island");
- assertEquals(maxArtIndex, 13);
+ assertTrue(maxArtIndex >= 14);
}
@Test
diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java
index 6e11ea051ca..04e97b50be8 100644
--- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java
+++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java
@@ -16,6 +16,7 @@ import forge.deck.io.DeckPreferences;
import forge.gamemodes.limited.BoosterDraft;
import forge.gamemodes.planarconquest.ConquestUtil;
import forge.gui.FThreads;
+import forge.gui.GuiBase;
import forge.gui.card.CardPreferences;
import forge.item.PaperCard;
import forge.itemmanager.CardManager;
@@ -768,7 +769,10 @@ public class FDeckEditor extends TabPageScreen {
}
protected void initialize() {
- cardManager.setup(config);
+ if (GuiBase.isAdventureMode())
+ cardManager.setup(config);
+ else //fix planar conquest deck editor and maybe others...
+ cardManager.setup(config, parentScreen.getColOverrides(config));
}
protected boolean canAddCards() {
diff --git a/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java b/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java
index 82195fb2593..7350f457952 100644
--- a/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java
+++ b/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java
@@ -70,6 +70,10 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView {
private final FLabel lblVariants = new FLabel.Builder().text(localizer.getMessage("lblVariants") + ":").font(VARIANTS_FONT).build();
private final FComboBox