mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
Merge branch 'master' of https://git.cardforge.org/core-developers/forge.git into questMode_wildOpponents
This commit is contained in:
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,15 +1,11 @@
|
||||
/*.idea
|
||||
/*.iml
|
||||
*.iml
|
||||
/*.tmp
|
||||
/.metadata
|
||||
/.recommenders
|
||||
forge-ai/forge-ai.iml
|
||||
forge-ai/target
|
||||
forge-core/forge-core.iml
|
||||
forge-core/target
|
||||
forge-game/*.iml
|
||||
forge-game/target
|
||||
forge-gui-android/*.iml
|
||||
forge-gui-android/*.keystore
|
||||
forge-gui-android/assets/fallback_skin/Thumbs.db
|
||||
forge-gui-android/bin
|
||||
@@ -47,20 +43,15 @@ forge-gui-android/res/values/bin
|
||||
forge-gui-android/res/values/gen
|
||||
forge-gui-android/res/values/target
|
||||
forge-gui-android/target
|
||||
forge-gui-desktop/*.iml
|
||||
forge-gui-desktop/target
|
||||
forge-gui-ios/*.iml
|
||||
forge-gui-ios/target
|
||||
forge-gui-mobile-dev/*.iml
|
||||
forge-gui-mobile-dev/bin
|
||||
forge-gui-mobile-dev/fallback_skin/Thumbs.db
|
||||
forge-gui-mobile-dev/res
|
||||
forge-gui-mobile-dev/target
|
||||
forge-gui-mobile-dev/testAssets
|
||||
forge-gui-mobile/*.iml
|
||||
forge-gui-mobile/bin
|
||||
forge-gui-mobile/target
|
||||
forge-gui/forge-gui.iml
|
||||
forge-gui/forge.profile.properties
|
||||
forge-gui/res/*.log
|
||||
forge-gui/res/PerSetTrackingResults
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.34-SNAPSHOT</version>
|
||||
<version>1.6.37-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-ai</artifactId>
|
||||
|
||||
@@ -24,6 +24,7 @@ import forge.ai.ability.AnimateAi;
|
||||
import forge.card.CardTypeView;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.ProtectEffect;
|
||||
import forge.game.card.*;
|
||||
@@ -37,6 +38,7 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.Expressions;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
@@ -464,7 +466,7 @@ public class AiAttackController {
|
||||
final CardCollectionView beastions = ai.getCardsIn(ZoneType.Battlefield, "Beastmaster Ascension");
|
||||
int minCreatures = 7;
|
||||
for (final Card beastion : beastions) {
|
||||
final int counters = beastion.getCounters(CounterType.QUEST);
|
||||
final int counters = beastion.getCounters(CounterEnumType.QUEST);
|
||||
minCreatures = Math.min(minCreatures, 7 - counters);
|
||||
}
|
||||
if (this.attackers.size() >= minCreatures) {
|
||||
@@ -739,6 +741,11 @@ public class AiAttackController {
|
||||
if (lightmineField) {
|
||||
doLightmineFieldAttackLogic(attackersLeft, numForcedAttackers, playAggro);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
if (bAssault) {
|
||||
if (LOG_AI_ATTACKS)
|
||||
@@ -1065,7 +1072,7 @@ public class AiAttackController {
|
||||
}
|
||||
}
|
||||
// if enough damage: switch to next planeswalker or player
|
||||
if (damage >= pw.getCounters(CounterType.LOYALTY)) {
|
||||
if (damage >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
||||
List<Card> pwDefending = combat.getDefendingPlaneswalkers();
|
||||
boolean found = false;
|
||||
// look for next planeswalker
|
||||
@@ -1135,7 +1142,6 @@ public class AiAttackController {
|
||||
// TODO Somehow subtract expected damage of other attacking creatures from enemy life total (how? other attackers not yet declared? Can the AI guesstimate which of their creatures will not get blocked?)
|
||||
if (attacker.getCurrentPower() * Integer.parseInt(attacker.getSVar("NonCombatPriority")) < ai.getOpponentsSmallestLifeTotal()) {
|
||||
// Check if the card actually has an ability the AI can and wants to play, if not, attacking is fine!
|
||||
boolean wantability = false;
|
||||
for (SpellAbility sa : attacker.getSpellAbilities()) {
|
||||
// Do not attack if we can afford using the ability.
|
||||
if (sa.isAbility()) {
|
||||
@@ -1164,6 +1170,9 @@ public class AiAttackController {
|
||||
return CombatUtil.canBlock(attacker, defender);
|
||||
}
|
||||
});
|
||||
|
||||
boolean canTrampleOverDefenders = attacker.hasKeyword(Keyword.TRAMPLE) && attacker.getNetCombatDamage() > Aggregates.sum(validBlockers, CardPredicates.Accessors.fnGetNetToughness);
|
||||
|
||||
// used to check that CanKillAllDangerous check makes sense in context where creatures with dangerous abilities are present
|
||||
boolean dangerousBlockersPresent = !CardLists.filter(validBlockers, Predicates.or(
|
||||
CardPredicates.hasKeyword(Keyword.WITHER), CardPredicates.hasKeyword(Keyword.INFECT),
|
||||
@@ -1192,7 +1201,7 @@ public class AiAttackController {
|
||||
if (isWorthLessThanAllKillers || canKillAllDangerous || numberOfPossibleBlockers < 2) {
|
||||
numberOfPossibleBlockers += 1;
|
||||
if (isWorthLessThanAllKillers && ComputerUtilCombat.canDestroyAttacker(ai, attacker, defender, combat, false)
|
||||
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterType.P1P1) == 0)) {
|
||||
&& !(attacker.hasKeyword(Keyword.UNDYING) && attacker.getCounters(CounterEnumType.P1P1) == 0)) {
|
||||
canBeKilledByOne = true; // there is a single creature on the battlefield that can kill the creature
|
||||
// see if the defending creature is of higher or lower
|
||||
// value. We don't want to attack only to lose value
|
||||
@@ -1253,6 +1262,10 @@ public class AiAttackController {
|
||||
if (LOG_AI_ATTACKS)
|
||||
System.out.println(attacker.getName() + " = attacking because they can't block, expecting to kill or damage player");
|
||||
return true;
|
||||
} else if (!canBeKilled && !dangerousBlockersPresent && canTrampleOverDefenders) {
|
||||
if (LOG_AI_ATTACKS)
|
||||
System.out.println(attacker.getName() + " = expecting to survive and get some Trample damage through");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (numberOfPossibleBlockers > 2
|
||||
@@ -1365,21 +1378,12 @@ public class AiAttackController {
|
||||
if (c.hasSVar("AIExertCondition")) {
|
||||
if (!c.getSVar("AIExertCondition").isEmpty()) {
|
||||
final String needsToExert = c.getSVar("AIExertCondition");
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
String sVar = needsToExert.split(" ")[0];
|
||||
String comparator = needsToExert.split(" ")[1];
|
||||
String compareTo = comparator.substring(2);
|
||||
try {
|
||||
x = Integer.parseInt(sVar);
|
||||
} catch (final NumberFormatException e) {
|
||||
x = CardFactoryUtil.xCount(c, c.getSVar(sVar));
|
||||
}
|
||||
try {
|
||||
y = Integer.parseInt(compareTo);
|
||||
} catch (final NumberFormatException e) {
|
||||
y = CardFactoryUtil.xCount(c, c.getSVar(compareTo));
|
||||
}
|
||||
|
||||
int x = AbilityUtils.calculateAmount(c, sVar, null);
|
||||
int y = AbilityUtils.calculateAmount(c, compareTo, null);
|
||||
if (Expressions.compare(x, comparator, y)) {
|
||||
shouldExert = true;
|
||||
}
|
||||
@@ -1493,4 +1497,39 @@ public class AiAttackController {
|
||||
attackersLeft.removeAll(attUnsafe);
|
||||
}
|
||||
|
||||
private boolean doRevengeOfRavensAttackLogic(Player ai, GameEntity defender, List<Card> attackersLeft, int numForcedAttackers, int maxAttack) {
|
||||
// TODO: detect Revenge of Ravens by the trigger instead of by name
|
||||
boolean revengeOfRavens = false;
|
||||
if (defender instanceof Player) {
|
||||
revengeOfRavens = !CardLists.filter(((Player)defender).getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Revenge of Ravens")).isEmpty();
|
||||
} else if (defender instanceof Card) {
|
||||
revengeOfRavens = !CardLists.filter(((Card)defender).getController().getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Revenge of Ravens")).isEmpty();
|
||||
}
|
||||
|
||||
if (!revengeOfRavens) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int life = ai.canLoseLife() && !ai.cantLoseForZeroOrLessLife() ? ai.getLife() : Integer.MAX_VALUE;
|
||||
maxAttack = maxAttack < 0 ? Integer.MAX_VALUE - 1 : maxAttack;
|
||||
if (Math.min(maxAttack, numForcedAttackers) >= life) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove all 1-power attackers since they usually only hurt the attacker
|
||||
// TODO: improve to account for possible combat effects coming from attackers like that
|
||||
CardCollection attUnsafe = new CardCollection();
|
||||
for (Card attacker : attackersLeft) {
|
||||
if (attacker.getNetCombatDamage() <= 1) {
|
||||
attUnsafe.add(attacker);
|
||||
}
|
||||
}
|
||||
attackersLeft.removeAll(attUnsafe);
|
||||
if (Math.min(maxAttack, attackersLeft.size()) >= life) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // end class ComputerUtil_Attack2
|
||||
|
||||
@@ -228,9 +228,9 @@ public class AiBlockController {
|
||||
// 3.Blockers that can destroy the attacker and have an upside when dying
|
||||
killingBlockers = getKillingBlockers(combat, attacker, blockers);
|
||||
for (Card b : killingBlockers) {
|
||||
if ((b.hasKeyword(Keyword.UNDYING) && b.getCounters(CounterType.P1P1) == 0) || b.hasSVar("SacMe")
|
||||
|| (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|
||||
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|
||||
if ((b.hasKeyword(Keyword.UNDYING) && b.getCounters(CounterEnumType.P1P1) == 0) || b.hasSVar("SacMe")
|
||||
|| (b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterEnumType.TIME) == 1)
|
||||
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterEnumType.FADE) == 0)
|
||||
|| b.hasSVar("EndOfTurnLeavePlay")) {
|
||||
blocker = b;
|
||||
break;
|
||||
@@ -299,8 +299,8 @@ public class AiBlockController {
|
||||
final List<Card> blockers = getPossibleBlockers(combat, attacker, blockersLeft, true);
|
||||
|
||||
for (Card b : blockers) {
|
||||
if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterType.TIME) == 1)
|
||||
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterType.FADE) == 0)
|
||||
if ((b.hasKeyword(Keyword.VANISHING) && b.getCounters(CounterEnumType.TIME) == 1)
|
||||
|| (b.hasKeyword(Keyword.FADING) && b.getCounters(CounterEnumType.FADE) == 0)
|
||||
|| b.hasSVar("EndOfTurnLeavePlay")) {
|
||||
blocker = b;
|
||||
if (!ComputerUtilCombat.canDestroyAttacker(ai, attacker, blocker, combat, false)) {
|
||||
@@ -851,7 +851,7 @@ public class AiBlockController {
|
||||
damageToPW += ComputerUtilCombat.predictDamageTo((Card) def, pwatkr.getNetCombatDamage(), pwatkr, true);
|
||||
}
|
||||
}
|
||||
if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= def.getCounters(CounterType.LOYALTY)) {
|
||||
if ((!onlyIfLethal && damageToPW > 0) || damageToPW >= def.getCounters(CounterEnumType.LOYALTY)) {
|
||||
threatenedPWs.add((Card) def);
|
||||
}
|
||||
}
|
||||
@@ -909,7 +909,7 @@ public class AiBlockController {
|
||||
damageToPW += ComputerUtilCombat.predictDamageTo(pw, pwAtk.getNetCombatDamage(), pwAtk, true);
|
||||
}
|
||||
}
|
||||
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterType.LOYALTY)) {
|
||||
if (!isFullyBlocked && damageToPW >= pw.getCounters(CounterEnumType.LOYALTY)) {
|
||||
for (Card chump : pwDefenders) {
|
||||
if (chosenChumpBlockers.contains(chump)) {
|
||||
combat.removeFromCombat(chump);
|
||||
|
||||
@@ -177,7 +177,7 @@ public class AiController {
|
||||
&& CardFactoryUtil.isCounterable(host)) {
|
||||
return true;
|
||||
} else if ("ChaliceOfTheVoid".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)
|
||||
&& host.getCMC() == c.getCounters(CounterType.CHARGE)) {
|
||||
&& host.getCMC() == c.getCounters(CounterEnumType.CHARGE)) {
|
||||
return true;
|
||||
} else if ("BazaarOfWonders".equals(curse) && sa.isSpell() && CardFactoryUtil.isCounterable(host)) {
|
||||
String hostName = host.getName();
|
||||
@@ -769,7 +769,7 @@ public class AiController {
|
||||
return AiPlayDecision.CantPlayAi;
|
||||
}
|
||||
}
|
||||
else if (sa.getPayCosts() != null){
|
||||
else {
|
||||
Cost payCosts = sa.getPayCosts();
|
||||
ManaCost mana = payCosts.getTotalMana();
|
||||
if (mana != null) {
|
||||
@@ -858,7 +858,7 @@ public class AiController {
|
||||
int neededMana = 0;
|
||||
boolean dangerousRecurringCost = false;
|
||||
|
||||
Cost costWithBuyback = sa.getPayCosts() != null ? sa.getPayCosts().copy() : Cost.Zero;
|
||||
Cost costWithBuyback = sa.getPayCosts().copy();
|
||||
for (OptionalCostValue opt : GameActionUtil.getOptionalCostValues(sa)) {
|
||||
if (opt.getType() == OptionalCost.Buyback) {
|
||||
costWithBuyback.add(opt.getCost());
|
||||
@@ -907,8 +907,8 @@ public class AiController {
|
||||
public int compare(final SpellAbility a, final SpellAbility b) {
|
||||
// sort from highest cost to lowest
|
||||
// we want the highest costs first
|
||||
int a1 = a.getPayCosts() == null ? 0 : a.getPayCosts().getTotalMana().getCMC();
|
||||
int b1 = b.getPayCosts() == null ? 0 : b.getPayCosts().getTotalMana().getCMC();
|
||||
int a1 = a.getPayCosts().getTotalMana().getCMC();
|
||||
int b1 = b.getPayCosts().getTotalMana().getCMC();
|
||||
|
||||
// deprioritize SAs explicitly marked as preferred to be activated last compared to all other SAs
|
||||
if (a.hasParam("AIActivateLast") && !b.hasParam("AIActivateLast")) {
|
||||
@@ -927,12 +927,12 @@ public class AiController {
|
||||
// deprioritize pump spells with pure energy cost (can be activated last,
|
||||
// since energy is generally scarce, plus can benefit e.g. Electrostatic Pummeler)
|
||||
int a2 = 0, b2 = 0;
|
||||
if (a.getApi() == ApiType.Pump && a.getPayCosts() != null && a.getPayCosts().getCostEnergy() != null) {
|
||||
if (a.getApi() == ApiType.Pump && a.getPayCosts().getCostEnergy() != null) {
|
||||
if (a.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
a2 = a.getPayCosts().getCostEnergy().convertAmount();
|
||||
}
|
||||
}
|
||||
if (b.getApi() == ApiType.Pump && b.getPayCosts() != null && b.getPayCosts().getCostEnergy() != null) {
|
||||
if (b.getApi() == ApiType.Pump && b.getPayCosts().getCostEnergy() != null) {
|
||||
if (b.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
b2 = b.getPayCosts().getCostEnergy().convertAmount();
|
||||
}
|
||||
@@ -956,8 +956,7 @@ public class AiController {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()
|
||||
&& a.getPayCosts() != null && b.getPayCosts() != null) {
|
||||
if (a.getHostCard().equals(b.getHostCard()) && a.getApi() == b.getApi()) {
|
||||
// Cheaper Spectacle costs should be preferred
|
||||
// FIXME: Any better way to identify that these are the same ability, one with Spectacle and one not?
|
||||
// (looks like it's not a full-fledged alternative cost as such, and is not processed with other alt costs)
|
||||
@@ -998,6 +997,10 @@ public class AiController {
|
||||
if (source.isEquipment() && noCreatures) {
|
||||
p -= 9;
|
||||
}
|
||||
// don't equip stuff in main 2 if there's more stuff to cast at the moment
|
||||
if (sa.getApi() == ApiType.Attach && !sa.isCurse() && source.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
p -= 1;
|
||||
}
|
||||
// 1. increase chance of using Surge effects
|
||||
// 2. non-surged versions are usually inefficient
|
||||
if (source.getOracleText().contains("surge cost") && !sa.isSurged()) {
|
||||
@@ -1479,7 +1482,7 @@ public class AiController {
|
||||
}
|
||||
|
||||
for (SpellAbility sa : card.getSpellAbilities()) {
|
||||
if (sa.getPayCosts() != null && sa.isAbility()
|
||||
if (sa.isAbility()
|
||||
&& sa.getPayCosts().getCostMana() != null
|
||||
&& sa.getPayCosts().getCostMana().getMana().getCMC() > 0
|
||||
&& (!sa.getPayCosts().hasTapCost() || !isTapLand)
|
||||
@@ -1802,7 +1805,7 @@ public class AiController {
|
||||
throw new UnsupportedOperationException("AI is not supposed to reach this code at the moment");
|
||||
}
|
||||
|
||||
public CardCollection chooseCardsForEffect(CardCollectionView pool, SpellAbility sa, int min, int max, boolean isOptional) {
|
||||
public CardCollection chooseCardsForEffect(CardCollectionView pool, SpellAbility sa, int min, int max, boolean isOptional, Map<String, Object> params) {
|
||||
if (sa == null || sa.getApi() == null) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@@ -1835,7 +1838,7 @@ public class AiController {
|
||||
default:
|
||||
CardCollection editablePool = new CardCollection(pool);
|
||||
for (int i = 0; i < max; i++) {
|
||||
Card c = player.getController().chooseSingleEntityForEffect(editablePool, sa, null, isOptional);
|
||||
Card c = player.getController().chooseSingleEntityForEffect(editablePool, sa, null, isOptional, params);
|
||||
if (c != null) {
|
||||
result.add(c);
|
||||
editablePool.remove(c);
|
||||
@@ -1986,6 +1989,35 @@ public class AiController {
|
||||
return MyRandom.getRandom().nextBoolean();
|
||||
}
|
||||
|
||||
public boolean chooseEvenOdd(SpellAbility sa) {
|
||||
String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (aiLogic.equals("AlwaysEven")) {
|
||||
return false; // false is Even
|
||||
} else if (aiLogic.equals("AlwaysOdd")) {
|
||||
return true; // true is Odd
|
||||
} else if (aiLogic.equals("Random")) {
|
||||
return MyRandom.getRandom().nextBoolean();
|
||||
} else if (aiLogic.equals("CMCInHand")) {
|
||||
CardCollectionView hand = sa.getActivatingPlayer().getCardsIn(ZoneType.Hand);
|
||||
int numEven = CardLists.filter(hand, CardPredicates.evenCMC()).size();
|
||||
int numOdd = CardLists.filter(hand, CardPredicates.oddCMC()).size();
|
||||
return numOdd > numEven;
|
||||
} else if (aiLogic.equals("CMCOppControls")) {
|
||||
CardCollectionView hand = sa.getActivatingPlayer().getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
int numEven = CardLists.filter(hand, CardPredicates.evenCMC()).size();
|
||||
int numOdd = CardLists.filter(hand, CardPredicates.oddCMC()).size();
|
||||
return numOdd > numEven;
|
||||
} else if (aiLogic.equals("CMCOppControlsByPower")) {
|
||||
// TODO: improve this to check for how dangerous those creatures actually are relative to host card
|
||||
CardCollectionView hand = sa.getActivatingPlayer().getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
int powerEven = Aggregates.sum(CardLists.filter(hand, CardPredicates.evenCMC()), Accessors.fnGetNetPower);
|
||||
int powerOdd = Aggregates.sum(CardLists.filter(hand, CardPredicates.oddCMC()), Accessors.fnGetNetPower);
|
||||
return powerOdd > powerEven;
|
||||
}
|
||||
return MyRandom.getRandom().nextBoolean(); // outside of any specific logic, choose randomly
|
||||
}
|
||||
|
||||
public Card chooseCardToHiddenOriginChangeZone(ZoneType destination, List<ZoneType> origin, SpellAbility sa,
|
||||
CardCollection fetchList, Player player2, Player decider) {
|
||||
if (useSimulation) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.card.CardType;
|
||||
@@ -16,12 +17,14 @@ import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import forge.util.TextUtil;
|
||||
import forge.util.collect.FCollectionView;
|
||||
|
||||
@@ -104,6 +107,24 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
}
|
||||
return PaymentDecision.card(randomSubset);
|
||||
}
|
||||
else if (type.equals("DifferentNames")) {
|
||||
CardCollection differentNames = new CardCollection();
|
||||
CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe"));
|
||||
while (c > 0) {
|
||||
Card chosen;
|
||||
if (!discardMe.isEmpty()) {
|
||||
chosen = Aggregates.random(discardMe);
|
||||
discardMe = CardLists.filter(discardMe, Predicates.not(CardPredicates.sharesNameWith(chosen)));
|
||||
} else {
|
||||
final Card worst = ComputerUtilCard.getWorstAI(hand);
|
||||
chosen = worst != null ? worst : Aggregates.random(hand);
|
||||
}
|
||||
differentNames.add(chosen);
|
||||
hand = CardLists.filter(hand, Predicates.not(CardPredicates.sharesNameWith(chosen)));
|
||||
c--;
|
||||
}
|
||||
return PaymentDecision.card(differentNames);
|
||||
}
|
||||
else {
|
||||
final AiController aic = ((PlayerControllerAi)player.getController()).getAi();
|
||||
|
||||
@@ -329,7 +350,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
}
|
||||
|
||||
CardCollectionView topLib = player.getCardsIn(ZoneType.Library, c);
|
||||
return topLib.size() < c ? null : PaymentDecision.card(topLib);
|
||||
return topLib.size() < c ? null : PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -494,7 +515,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
for (final SpellAbility sa : card.getSpellAbilities()) {
|
||||
if (sa.isManaAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -627,41 +648,41 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
// the first things are benefit from removing counters
|
||||
|
||||
// try to remove +1/+1 counter from undying creature
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.P1P1, c),
|
||||
List<Card> prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.P1P1, c),
|
||||
CardPredicates.hasKeyword("Undying"));
|
||||
|
||||
if (!prefs.isEmpty()) {
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.P1P1));
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.P1P1));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.P1P1;
|
||||
result.ct = CounterType.get(CounterEnumType.P1P1);
|
||||
return result;
|
||||
}
|
||||
|
||||
// try to remove -1/-1 counter from persist creature
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.M1M1, c),
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.M1M1, c),
|
||||
CardPredicates.hasKeyword("Persist"));
|
||||
|
||||
if (!prefs.isEmpty()) {
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.M1M1));
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.M1M1));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.M1M1;
|
||||
result.ct = CounterType.get(CounterEnumType.M1M1);
|
||||
return result;
|
||||
}
|
||||
|
||||
// try to remove Time counter from Chronozoa, it will generate more
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.TIME, c),
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.TIME, c),
|
||||
CardPredicates.nameEquals("Chronozoa"));
|
||||
|
||||
if (!prefs.isEmpty()) {
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterType.TIME));
|
||||
Collections.sort(prefs, CardPredicates.compareByCounterType(CounterEnumType.TIME));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.TIME;
|
||||
result.ct = CounterType.get(CounterEnumType.TIME);
|
||||
return result;
|
||||
}
|
||||
|
||||
// try to remove Quest counter on something with enough counters for the
|
||||
// effect to continue
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterType.QUEST, c));
|
||||
prefs = CardLists.filter(typeList, CardPredicates.hasCounter(CounterEnumType.QUEST, c));
|
||||
|
||||
if (!prefs.isEmpty()) {
|
||||
prefs = CardLists.filter(prefs, new Predicate<Card>() {
|
||||
@@ -673,12 +694,12 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
if (crd.hasSVar("MaxQuestEffect")) {
|
||||
e = Integer.parseInt(crd.getSVar("MaxQuestEffect"));
|
||||
}
|
||||
return crd.getCounters(CounterType.QUEST) >= e + c;
|
||||
return crd.getCounters(CounterEnumType.QUEST) >= e + c;
|
||||
}
|
||||
});
|
||||
Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterType.QUEST)));
|
||||
Collections.sort(prefs, Collections.reverseOrder(CardPredicates.compareByCounterType(CounterEnumType.QUEST)));
|
||||
PaymentDecision result = PaymentDecision.card(prefs);
|
||||
result.ct = CounterType.QUEST;
|
||||
result.ct = CounterType.get(CounterEnumType.QUEST);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -775,7 +796,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
for (Map.Entry<CounterType, Integer> e : crd.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey() == CounterType.valueOf(ctr))) {
|
||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -787,7 +808,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
PaymentDecision result = PaymentDecision.card(card);
|
||||
|
||||
for (Map.Entry<CounterType, Integer> e : card.getCounters().entrySet()) {
|
||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey() == CounterType.valueOf(ctr))) {
|
||||
if (e.getValue() >= c && (ctr.equals("ANY") || e.getKey().equals(CounterType.getType(ctr)))) {
|
||||
result.ct = e.getKey();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ public enum AiProps { /** */
|
||||
MOVE_EQUIPMENT_TO_BETTER_CREATURES ("from_useless_only"),
|
||||
MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD ("60"),
|
||||
PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS ("true"),
|
||||
SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD ("400"),
|
||||
PREDICT_SPELLS_FOR_MAIN2 ("true"), /** */
|
||||
RESERVE_MANA_FOR_MAIN2_CHANCE ("0"), /** */
|
||||
PLAY_AGGRO ("false"),
|
||||
|
||||
@@ -349,8 +349,8 @@ public class ComputerUtil {
|
||||
for (int ip = 0; ip < 6; ip++) {
|
||||
final int priority = 6 - ip;
|
||||
if (priority == 2 && ai.isCardInPlay("Crucible of Worlds")) {
|
||||
CardCollection landsInPlay = CardLists.getType(typeList, "Land");
|
||||
if (!landsInPlay.isEmpty()) {
|
||||
CardCollection landsInPlay = CardLists.getType(typeList, "Land");
|
||||
if (!landsInPlay.isEmpty()) {
|
||||
// Don't need more land.
|
||||
return ComputerUtilCard.getWorstLand(landsInPlay);
|
||||
}
|
||||
@@ -382,13 +382,13 @@ public class ComputerUtil {
|
||||
|
||||
// try everything when about to die
|
||||
if (game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
|
||||
final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature");
|
||||
if (!nonCreatures.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(nonCreatures);
|
||||
} else if (!typeList.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(typeList);
|
||||
}
|
||||
&& ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
|
||||
final CardCollection nonCreatures = CardLists.getNotType(typeList, "Creature");
|
||||
if (!nonCreatures.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(nonCreatures);
|
||||
} else if (!typeList.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(typeList);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pref.contains("DiscardCost")) { // search for permanents with DiscardMe
|
||||
@@ -454,10 +454,10 @@ public class ComputerUtil {
|
||||
// try everything when about to die
|
||||
if (activate != null && "Reality Smasher".equals(activate.getName()) ||
|
||||
game.getPhaseHandler().getPhase().equals(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
|
||||
if (!typeList.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(typeList);
|
||||
}
|
||||
&& ComputerUtilCombat.lifeInSeriousDanger(ai, game.getCombat())) {
|
||||
if (!typeList.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(typeList);
|
||||
}
|
||||
}
|
||||
} else if (pref.contains("DonateMe")) {
|
||||
// search for permanents with DonateMe. priority 1 is the lowest, priority 5 the highest
|
||||
@@ -718,9 +718,9 @@ public class ComputerUtil {
|
||||
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold");
|
||||
|
||||
if ("OpponentOnly".equals(source.getParam("AILogic"))) {
|
||||
if(!source.getActivatingPlayer().isOpponentOf(ai)) {
|
||||
return sacrificed; // sacrifice none
|
||||
}
|
||||
if(!source.getActivatingPlayer().isOpponentOf(ai)) {
|
||||
return sacrificed; // sacrifice none
|
||||
}
|
||||
} else if ("DesecrationDemon".equals(source.getParam("AILogic"))) {
|
||||
if (!SpecialCardAi.DesecrationDemon.considerSacrificingCreature(ai, source)) {
|
||||
return sacrificed; // don't sacrifice unless in special conditions specified by DesecrationDemon AI
|
||||
@@ -738,27 +738,27 @@ public class ComputerUtil {
|
||||
boolean removedSelf = false;
|
||||
|
||||
if (isOptional && source.hasParam("Devour") || source.hasParam("Exploit") || considerSacLogic) {
|
||||
if (source.hasParam("Exploit")) {
|
||||
for (Trigger t : host.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.Exploited) {
|
||||
final String execute = t.getParam("Execute");
|
||||
if (execute == null) {
|
||||
continue;
|
||||
}
|
||||
final SpellAbility exSA = AbilityFactory.getAbility(host.getSVar(execute), host);
|
||||
if (source.hasParam("Exploit")) {
|
||||
for (Trigger t : host.getTriggers()) {
|
||||
if (t.getMode() == TriggerType.Exploited) {
|
||||
final String execute = t.getParam("Execute");
|
||||
if (execute == null) {
|
||||
continue;
|
||||
}
|
||||
final SpellAbility exSA = AbilityFactory.getAbility(host.getSVar(execute), host);
|
||||
|
||||
exSA.setActivatingPlayer(ai);
|
||||
exSA.setTrigger(true);
|
||||
exSA.setActivatingPlayer(ai);
|
||||
exSA.setTrigger(true);
|
||||
|
||||
// Run non-mandatory trigger.
|
||||
// These checks only work if the Executing SpellAbility is an Ability_Sub.
|
||||
if ((exSA instanceof AbilitySub) && !SpellApiToAi.Converter.get(exSA.getApi()).doTriggerAI(ai, exSA, false)) {
|
||||
// AI would not run this trigger if given the chance
|
||||
return sacrificed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Run non-mandatory trigger.
|
||||
// These checks only work if the Executing SpellAbility is an Ability_Sub.
|
||||
if ((exSA instanceof AbilitySub) && !SpellApiToAi.Converter.get(exSA.getApi()).doTriggerAI(ai, exSA, false)) {
|
||||
// AI would not run this trigger if given the chance
|
||||
return sacrificed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
remaining = CardLists.filter(remaining, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
@@ -958,16 +958,16 @@ public class ComputerUtil {
|
||||
final Card card = sa.getHostCard();
|
||||
|
||||
if (card.hasSVar("PlayMain1")) {
|
||||
if (card.getSVar("PlayMain1").equals("ALWAYS") || sa.getPayCosts().hasNoManaCost()) {
|
||||
return true;
|
||||
} else if (card.getSVar("PlayMain1").equals("OPPONENTCREATURES")) {
|
||||
//Only play these main1 when the opponent has creatures (stealing and giving them haste)
|
||||
if (!ai.getOpponents().getCreaturesInPlay().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
} else if (!card.getController().getCreaturesInPlay().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (card.getSVar("PlayMain1").equals("ALWAYS") || sa.getPayCosts().hasNoManaCost()) {
|
||||
return true;
|
||||
} else if (card.getSVar("PlayMain1").equals("OPPONENTCREATURES")) {
|
||||
//Only play these main1 when the opponent has creatures (stealing and giving them haste)
|
||||
if (!ai.getOpponents().getCreaturesInPlay().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
} else if (!card.getController().getCreaturesInPlay().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// try not to cast Raid creatures in main 1 if an attack is likely
|
||||
@@ -980,7 +980,7 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
if (card.getManaCost().isZero()) {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (card.hasKeyword(Keyword.RIOT) && ChooseGenericEffectAi.preferHasteForRiot(sa, ai)) {
|
||||
@@ -1009,8 +1009,8 @@ public class ComputerUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (card.hasKeyword(Keyword.EXALTED)) {
|
||||
return true;
|
||||
if (card.hasKeyword(Keyword.EXALTED) || card.hasKeyword(Keyword.EXTORT)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//cast equipments in Main1 when there are creatures to equip and no other unequipped equipment
|
||||
@@ -1053,6 +1053,12 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ApiType.PermanentNoncreature.equals(sa.getApi()) && buffedcard.hasKeyword(Keyword.PROWESS)) {
|
||||
// non creature Permanent spell
|
||||
return true;
|
||||
}
|
||||
|
||||
if (card.hasKeyword(Keyword.SOULBOND) && buffedcard.isCreature() && !buffedcard.isPaired()) {
|
||||
return true;
|
||||
}
|
||||
@@ -1219,6 +1225,9 @@ public class ComputerUtil {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (ApiType.PermanentNoncreature.equals(sa.getApi()) && buffedCard.hasKeyword(Keyword.PROWESS)) {
|
||||
return true;
|
||||
}
|
||||
//Fill the graveyard for Threshold
|
||||
if (checkThreshold) {
|
||||
for (StaticAbility stAb : buffedCard.getStaticAbilities()) {
|
||||
@@ -1253,7 +1262,7 @@ public class ComputerUtil {
|
||||
int activations = sa.getActivationsThisTurn();
|
||||
|
||||
if (!sa.isIntrinsic()) {
|
||||
return MyRandom.getRandom().nextFloat() >= .95; // Abilities created by static abilities have no memory
|
||||
return MyRandom.getRandom().nextFloat() >= .95; // Abilities created by static abilities have no memory
|
||||
}
|
||||
|
||||
if (activations < 10) { //10 activations per turn should still be acceptable
|
||||
@@ -1270,13 +1279,13 @@ public class ComputerUtil {
|
||||
return false;
|
||||
}
|
||||
if (abCost.hasTapCost() && source.hasSVar("AITapDown")) {
|
||||
return true;
|
||||
return true;
|
||||
} else if (sa.hasParam("Planeswalker") && ai.getGame().getPhaseHandler().is(PhaseType.MAIN2)) {
|
||||
for (final CostPart part : abCost.getCostParts()) {
|
||||
if (part instanceof CostPutCounter) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (final CostPart part : abCost.getCostParts()) {
|
||||
if (part instanceof CostPutCounter) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final CostPart part : abCost.getCostParts()) {
|
||||
if (part instanceof CostSacrifice) {
|
||||
@@ -1332,7 +1341,7 @@ public class ComputerUtil {
|
||||
|
||||
final String affected = params.get("Affected");
|
||||
if (affected.contains("Creature.YouCtrl")
|
||||
|| affected.contains("Other+YouCtrl")) {
|
||||
|| affected.contains("Other+YouCtrl")) {
|
||||
return true;
|
||||
} else if (affected.contains("Creature.PairedWith") && !c.isPaired()) {
|
||||
return true;
|
||||
@@ -1343,8 +1352,8 @@ public class ComputerUtil {
|
||||
for (Trigger t : c.getTriggers()) {
|
||||
Map<String, String> params = t.getMapParams();
|
||||
if (!"ChangesZone".equals(params.get("Mode"))
|
||||
|| !"Battlefield".equals(params.get("Destination"))
|
||||
|| !params.containsKey("ValidCard")) {
|
||||
|| !"Battlefield".equals(params.get("Destination"))
|
||||
|| !params.containsKey("ValidCard")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1352,7 +1361,7 @@ public class ComputerUtil {
|
||||
if (valid.contains("Creature.YouCtrl")
|
||||
|| valid.contains("Other+YouCtrl") ) {
|
||||
|
||||
final SpellAbility sa = t.getTriggeredSA();
|
||||
final SpellAbility sa = t.getOverridingAbility();
|
||||
if (sa != null && sa.getApi() == ApiType.Pump && sa.hasParam("KW")
|
||||
&& sa.getParam("KW").contains("Haste")) {
|
||||
return true;
|
||||
@@ -1557,7 +1566,7 @@ public class ComputerUtil {
|
||||
CardCollectionView battleField = aiPlayer.getCardsIn(ZoneType.Battlefield);
|
||||
objects = CardLists.getValidCards(battleField, topStack.getParam("ValidCards").split(","), source.getController(), source, topStack);
|
||||
} else {
|
||||
return threatened;
|
||||
return threatened;
|
||||
}
|
||||
} else {
|
||||
objects = topStack.getTargets().getTargets();
|
||||
@@ -1571,7 +1580,7 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
if (canBeTargeted.isEmpty()) {
|
||||
return threatened;
|
||||
return threatened;
|
||||
}
|
||||
objects = canBeTargeted;
|
||||
}
|
||||
@@ -1813,7 +1822,7 @@ public class ComputerUtil {
|
||||
}
|
||||
//GainControl
|
||||
else if ((threatApi == ApiType.GainControl
|
||||
|| (threatApi == ApiType.Attach && topStack.hasParam("AILogic") && topStack.getParam("AILogic").equals("GainControl") ))
|
||||
|| (threatApi == ApiType.Attach && topStack.hasParam("AILogic") && topStack.getParam("AILogic").equals("GainControl") ))
|
||||
&& (saviourApi == ApiType.ChangeZone || saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|
||||
|| saviourApi == ApiType.Protection || saviourApi == null)) {
|
||||
for (final Object o : objects) {
|
||||
@@ -2201,10 +2210,13 @@ public class ComputerUtil {
|
||||
return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max);
|
||||
}
|
||||
|
||||
public static String chooseSomeType(Player ai, String kindOfType, String logic, List<String> invalidTypes) {
|
||||
public static String chooseSomeType(Player ai, String kindOfType, String logic, Collection<String> validTypes, List<String> invalidTypes) {
|
||||
if (invalidTypes == null) {
|
||||
invalidTypes = ImmutableList.of();
|
||||
}
|
||||
if (validTypes == null) {
|
||||
validTypes = ImmutableList.of();
|
||||
}
|
||||
|
||||
final Game game = ai.getGame();
|
||||
String chosen = "";
|
||||
@@ -2228,7 +2240,7 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
if (StringUtils.isEmpty(chosen)) {
|
||||
chosen = "Creature";
|
||||
chosen = validTypes.isEmpty() ? "Creature" : Aggregates.random(validTypes);
|
||||
}
|
||||
} else if (kindOfType.equals("Creature")) {
|
||||
if (logic != null) {
|
||||
@@ -2242,7 +2254,7 @@ public class ComputerUtil {
|
||||
chosen = ComputerUtilCard.getMostProminentType(ai.getCardsIn(ZoneType.Battlefield), valid);
|
||||
}
|
||||
else if (logic.equals("MostProminentOppControls")) {
|
||||
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
CardCollection list = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
|
||||
chosen = ComputerUtilCard.getMostProminentType(list, valid);
|
||||
if (!CardType.isACreatureType(chosen) || invalidTypes.contains(chosen)) {
|
||||
list = CardLists.filterControlledBy(game.getCardsInGame(), ai.getOpponents());
|
||||
@@ -2330,6 +2342,8 @@ public class ComputerUtil {
|
||||
|
||||
boolean opponent = controller.isOpponentOf(ai);
|
||||
|
||||
final CounterType p1p1Type = CounterType.get(CounterEnumType.P1P1);
|
||||
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return Aggregates.random(options);
|
||||
}
|
||||
@@ -2383,7 +2397,7 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
// is it can't receive counters, choose +1/+1 ones
|
||||
if (!source.canReceiveCounters(CounterType.P1P1)) {
|
||||
if (!source.canReceiveCounters(p1p1Type)) {
|
||||
return opponent ? "Feather" : "Quill";
|
||||
}
|
||||
// if source is not on the battlefield anymore, choose +1/+1
|
||||
@@ -2415,7 +2429,7 @@ public class ComputerUtil {
|
||||
Card token = TokenAi.spawnToken(controller, saToken);
|
||||
|
||||
// is it can't receive counters, choose +1/+1 ones
|
||||
if (!source.canReceiveCounters(CounterType.P1P1)) {
|
||||
if (!source.canReceiveCounters(p1p1Type)) {
|
||||
return opponent ? "Strength" : "Numbers";
|
||||
}
|
||||
|
||||
@@ -2425,7 +2439,7 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
// token would not survive
|
||||
if (token == null) {
|
||||
if (token == null || !token.isCreature() || token.getNetToughness() < 1) {
|
||||
return opponent ? "Numbers" : "Strength";
|
||||
}
|
||||
|
||||
@@ -2438,11 +2452,11 @@ public class ComputerUtil {
|
||||
Card sourceNumbers = CardUtil.getLKICopy(source);
|
||||
Card sourceStrength = CardUtil.getLKICopy(source);
|
||||
|
||||
sourceNumbers.setCounters(CounterType.P1P1, sourceNumbers.getCounters(CounterType.P1P1) + numStrength);
|
||||
sourceNumbers.setCounters(p1p1Type, sourceNumbers.getCounters(p1p1Type) + numStrength);
|
||||
sourceNumbers.setZone(source.getZone());
|
||||
|
||||
sourceStrength.setCounters(CounterType.P1P1,
|
||||
sourceStrength.getCounters(CounterType.P1P1) + numStrength + 1);
|
||||
sourceStrength.setCounters(p1p1Type,
|
||||
sourceStrength.getCounters(p1p1Type) + numStrength + 1);
|
||||
sourceStrength.setZone(source.getZone());
|
||||
|
||||
int scoreStrength = ComputerUtilCard.evaluateCreature(sourceStrength) + tokenScore * numNumbers;
|
||||
@@ -2464,7 +2478,7 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
// is it can't receive counters, choose +1/+1 ones
|
||||
if (!source.canReceiveCounters(CounterType.P1P1)) {
|
||||
if (!source.canReceiveCounters(p1p1Type)) {
|
||||
return opponent ? "Sprout" : "Harvest";
|
||||
}
|
||||
|
||||
@@ -2756,27 +2770,27 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
public static boolean isNegativeCounter(CounterType type, Card c) {
|
||||
return type == CounterType.AGE || type == CounterType.BRIBERY || type == CounterType.DOOM
|
||||
|| type == CounterType.M1M1 || type == CounterType.M0M2 || type == CounterType.M0M1
|
||||
|| type == CounterType.M1M0 || type == CounterType.M2M1 || type == CounterType.M2M2
|
||||
return type.is(CounterEnumType.AGE) || type.is(CounterEnumType.BRIBERY) || type.is(CounterEnumType.DOOM)
|
||||
|| type.is(CounterEnumType.M1M1) || type.is(CounterEnumType.M0M2) || type.is(CounterEnumType.M0M1)
|
||||
|| type.is(CounterEnumType.M1M0) || type.is(CounterEnumType.M2M1) || type.is(CounterEnumType.M2M2)
|
||||
// Blaze only hurts Lands
|
||||
|| (type == CounterType.BLAZE && c.isLand())
|
||||
|| (type.is(CounterEnumType.BLAZE) && c.isLand())
|
||||
// Iceberg does use Ice as Storage
|
||||
|| (type == CounterType.ICE && !"Iceberg".equals(c.getName()))
|
||||
|| (type.is(CounterEnumType.ICE) && !"Iceberg".equals(c.getName()))
|
||||
// some lands does use Depletion as Storage Counter
|
||||
|| (type == CounterType.DEPLETION && c.hasKeyword("CARDNAME doesn't untap during your untap step."))
|
||||
|| (type.is(CounterEnumType.DEPLETION) && c.hasKeyword("CARDNAME doesn't untap during your untap step."))
|
||||
// treat Time Counters on suspended Cards as Bad,
|
||||
// and also on Chronozoa
|
||||
|| (type == CounterType.TIME && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
||||
|| type == CounterType.GOLD || type == CounterType.MUSIC || type == CounterType.PUPA
|
||||
|| type == CounterType.PARALYZATION || type == CounterType.SHELL || type == CounterType.SLEEP
|
||||
|| type == CounterType.SLUMBER || type == CounterType.SLEIGHT || type == CounterType.WAGE;
|
||||
|| (type.is(CounterEnumType.TIME) && (!c.isInPlay() || "Chronozoa".equals(c.getName())))
|
||||
|| type.is(CounterEnumType.GOLD) || type.is(CounterEnumType.MUSIC) || type.is(CounterEnumType.PUPA)
|
||||
|| type.is(CounterEnumType.PARALYZATION) || type.is(CounterEnumType.SHELL) || type.is(CounterEnumType.SLEEP)
|
||||
|| type.is(CounterEnumType.SLUMBER) || type.is(CounterEnumType.SLEIGHT) || type.is(CounterEnumType.WAGE);
|
||||
}
|
||||
|
||||
// this countertypes has no effect
|
||||
public static boolean isUselessCounter(CounterType type) {
|
||||
return type == CounterType.AWAKENING || type == CounterType.MANIFESTATION || type == CounterType.PETRIFICATION
|
||||
|| type == CounterType.TRAINING;
|
||||
return type.is(CounterEnumType.AWAKENING) || type.is(CounterEnumType.MANIFESTATION) || type.is(CounterEnumType.PETRIFICATION)
|
||||
|| type.is(CounterEnumType.TRAINING);
|
||||
}
|
||||
|
||||
public static Player evaluateBoardPosition(final List<Player> listToEvaluate) {
|
||||
|
||||
@@ -113,9 +113,7 @@ public class ComputerUtilAbility {
|
||||
List<SpellAbility> priorityAltSa = Lists.newArrayList();
|
||||
List<SpellAbility> otherAltSa = Lists.newArrayList();
|
||||
for (SpellAbility altSa : saAltCosts) {
|
||||
if (altSa.getPayCosts() == null || sa.getPayCosts() == null) {
|
||||
otherAltSa.add(altSa);
|
||||
} else if (sa.getPayCosts().isOnlyManaCost()
|
||||
if (sa.getPayCosts().isOnlyManaCost()
|
||||
&& altSa.getPayCosts().isOnlyManaCost() && sa.getPayCosts().getTotalMana().compareTo(altSa.getPayCosts().getTotalMana()) == 1) {
|
||||
// the alternative cost is strictly cheaper, so why not? (e.g. Omniscience etc.)
|
||||
priorityAltSa.add(altSa);
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.card.CardType;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
@@ -172,21 +171,22 @@ public class ComputerUtilCard {
|
||||
|
||||
// if no non-basic lands, target the least represented basic land type
|
||||
String sminBL = "";
|
||||
int iminBL = 20000; // hopefully no one will ever have more than 20000
|
||||
// lands of one type....
|
||||
int iminBL = Integer.MAX_VALUE;
|
||||
int n = 0;
|
||||
for (String name : MagicColor.Constant.BASIC_LANDS) {
|
||||
n = CardLists.getType(land, name).size();
|
||||
if ((n < iminBL) && (n > 0)) {
|
||||
// if two or more are tied, only the
|
||||
// first
|
||||
// one checked will be used
|
||||
if (n < iminBL && n > 0) {
|
||||
iminBL = n;
|
||||
sminBL = name;
|
||||
}
|
||||
}
|
||||
if (iminBL == 20000) {
|
||||
return null; // no basic land was a minimum
|
||||
if (iminBL == Integer.MAX_VALUE) {
|
||||
// All basic lands have no basic land type. Just return something
|
||||
Iterator<Card> untapped = Iterables.filter(land, CardPredicates.Presets.UNTAPPED).iterator();
|
||||
if (untapped.hasNext()) {
|
||||
return untapped.next();
|
||||
}
|
||||
return land.get(0);
|
||||
}
|
||||
|
||||
final List<Card> bLand = CardLists.getType(land, sminBL);
|
||||
@@ -195,7 +195,6 @@ public class ComputerUtilCard {
|
||||
return ut;
|
||||
}
|
||||
|
||||
|
||||
return Aggregates.random(bLand); // random tapped land of least represented type
|
||||
}
|
||||
|
||||
@@ -1423,8 +1422,8 @@ public class ComputerUtilCard {
|
||||
if (combat.isAttacking(c) && opp.getLife() > 0) {
|
||||
int dmg = ComputerUtilCombat.damageIfUnblocked(c, opp, combat, true);
|
||||
int pumpedDmg = ComputerUtilCombat.damageIfUnblocked(pumped, opp, pumpedCombat, true);
|
||||
int poisonOrig = opp.canReceiveCounters(CounterType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(c, ai) : 0;
|
||||
int poisonPumped = opp.canReceiveCounters(CounterType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(pumped, ai) : 0;
|
||||
int poisonOrig = opp.canReceiveCounters(CounterEnumType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(c, ai) : 0;
|
||||
int poisonPumped = opp.canReceiveCounters(CounterEnumType.POISON) ? ComputerUtilCombat.poisonIfUnblocked(pumped, ai) : 0;
|
||||
|
||||
// predict Infect
|
||||
if (pumpedDmg == 0 && c.hasKeyword(Keyword.INFECT)) {
|
||||
@@ -1447,7 +1446,7 @@ public class ComputerUtilCard {
|
||||
}
|
||||
if (pumpedDmg > dmg) {
|
||||
if ((!c.hasKeyword(Keyword.INFECT) && pumpedDmg >= opp.getLife())
|
||||
|| (c.hasKeyword(Keyword.INFECT) && opp.canReceiveCounters(CounterType.POISON) && pumpedDmg >= opp.getPoisonCounters())
|
||||
|| (c.hasKeyword(Keyword.INFECT) && opp.canReceiveCounters(CounterEnumType.POISON) && pumpedDmg >= opp.getPoisonCounters())
|
||||
|| ("PumpForTrample".equals(sa.getParam("AILogic")))) {
|
||||
return true;
|
||||
}
|
||||
@@ -1475,7 +1474,7 @@ public class ComputerUtilCard {
|
||||
if (totalPowerUnblocked >= opp.getLife()) {
|
||||
return true;
|
||||
} else if (totalPowerUnblocked > dmg && sa.getHostCard() != null && sa.getHostCard().isInPlay()) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost()) {
|
||||
if (sa.getPayCosts().hasNoManaCost()) {
|
||||
return true; // always activate abilities which cost no mana and which can increase unblocked damage
|
||||
}
|
||||
}
|
||||
@@ -1766,10 +1765,10 @@ public class ComputerUtilCard {
|
||||
}
|
||||
|
||||
public static boolean hasActiveUndyingOrPersist(final Card c) {
|
||||
if (c.hasKeyword(Keyword.UNDYING) && c.getCounters(CounterType.P1P1) == 0) {
|
||||
if (c.hasKeyword(Keyword.UNDYING) && c.getCounters(CounterEnumType.P1P1) == 0) {
|
||||
return true;
|
||||
}
|
||||
if (c.hasKeyword(Keyword.PERSIST) && c.getCounters(CounterType.M1M1) == 0) {
|
||||
if (c.hasKeyword(Keyword.PERSIST) && c.getCounters(CounterEnumType.M1M1) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1786,10 +1785,6 @@ public class ComputerUtilCard {
|
||||
|
||||
for (Card c : otb) {
|
||||
for (SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.getPayCosts() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CostPayEnergy energyCost = sa.getPayCosts().getCostEnergy();
|
||||
if (energyCost != null) {
|
||||
int amount = energyCost.convertAmount();
|
||||
@@ -1861,7 +1856,7 @@ public class ComputerUtilCard {
|
||||
|
||||
public static AiPlayDecision checkNeedsToPlayReqs(final Card card, final SpellAbility sa) {
|
||||
Game game = card.getGame();
|
||||
boolean isRightSplit = sa != null && sa.isRightSplit();
|
||||
boolean isRightSplit = sa != null && sa.getCardState() != null;
|
||||
String needsToPlayName = isRightSplit ? "SplitNeedsToPlay" : "NeedsToPlay";
|
||||
String needsToPlayVarName = isRightSplit ? "SplitNeedsToPlayVar" : "NeedsToPlayVar";
|
||||
|
||||
@@ -1913,21 +1908,12 @@ public class ComputerUtilCard {
|
||||
}
|
||||
if (card.getSVar(needsToPlayVarName).length() > 0) {
|
||||
final String needsToPlay = card.getSVar(needsToPlayVarName);
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
String sVar = needsToPlay.split(" ")[0];
|
||||
String comparator = needsToPlay.split(" ")[1];
|
||||
String compareTo = comparator.substring(2);
|
||||
try {
|
||||
x = Integer.parseInt(sVar);
|
||||
} catch (final NumberFormatException e) {
|
||||
x = CardFactoryUtil.xCount(card, card.getSVar(sVar));
|
||||
}
|
||||
try {
|
||||
y = Integer.parseInt(compareTo);
|
||||
} catch (final NumberFormatException e) {
|
||||
y = CardFactoryUtil.xCount(card, card.getSVar(compareTo));
|
||||
}
|
||||
int x = AbilityUtils.calculateAmount(card, sVar, sa);
|
||||
int y = AbilityUtils.calculateAmount(card, compareTo, sa);
|
||||
|
||||
if (!Expressions.compare(x, comparator, y)) {
|
||||
return AiPlayDecision.NeedsToPlayCriteriaNotMet;
|
||||
}
|
||||
@@ -1944,4 +1930,7 @@ public class ComputerUtilCard {
|
||||
public static boolean isCardRemRandomDeck(final Card card) {
|
||||
return card.getRules() != null && card.getRules().getAiHints().getRemRandomDecks();
|
||||
}
|
||||
public static boolean isCardRemNonCommanderDeck(final Card card) {
|
||||
return card.getRules() != null && card.getRules().getAiHints().getRemNonCommanderDecks();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +328,7 @@ public class ComputerUtilCombat {
|
||||
public static int resultingPoison(final Player ai, final Combat combat) {
|
||||
|
||||
// ai can't get poision counters, so the value can't change
|
||||
if (!ai.canReceiveCounters(CounterType.POISON)) {
|
||||
if (!ai.canReceiveCounters(CounterEnumType.POISON)) {
|
||||
return ai.getPoisonCounters();
|
||||
}
|
||||
|
||||
@@ -931,7 +931,7 @@ public class ComputerUtilCombat {
|
||||
if (dealsFirstStrikeDamage(attacker, withoutAbilities, null)
|
||||
&& (attacker.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT))
|
||||
&& !dealsFirstStrikeDamage(blocker, withoutAbilities, null)
|
||||
&& !blocker.canReceiveCounters(CounterType.M1M1)) {
|
||||
&& !blocker.canReceiveCounters(CounterEnumType.M1M1)) {
|
||||
power -= attacker.getNetCombatDamage();
|
||||
}
|
||||
|
||||
@@ -973,62 +973,45 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
theTriggers.addAll(attacker.getTriggers());
|
||||
for (final Trigger trigger : theTriggers) {
|
||||
final Map<String, String> trigParams = trigger.getMapParams();
|
||||
final Card source = trigger.getHostCard();
|
||||
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, String> abilityParams = null;
|
||||
if (trigger.getOverridingAbility() != null) {
|
||||
abilityParams = trigger.getOverridingAbility().getMapParams();
|
||||
} else if (trigParams.containsKey("Execute")) {
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
abilityParams = AbilityFactory.getMapParams(ability);
|
||||
} else {
|
||||
SpellAbility sa = trigger.ensureAbility();
|
||||
if (sa == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump")) {
|
||||
if (!ApiType.Pump.equals(sa.getApi())) {
|
||||
continue;
|
||||
}
|
||||
if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump")) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
continue;
|
||||
}
|
||||
if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) {
|
||||
continue; // targeted pumping not supported
|
||||
|
||||
if (!sa.hasParam("NumAtt")) {
|
||||
continue;
|
||||
}
|
||||
final List<Card> list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
|
||||
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
||||
|
||||
String defined = sa.getParam("Defined");
|
||||
final List<Card> list = AbilityUtils.getDefinedCards(source, defined, sa);
|
||||
if ("TriggeredBlocker".equals(defined)) {
|
||||
list.add(blocker);
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!list.contains(blocker)) {
|
||||
continue;
|
||||
}
|
||||
if (!abilityParams.containsKey("NumAtt")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String att = abilityParams.get("NumAtt");
|
||||
if (att.startsWith("+")) {
|
||||
att = att.substring(1);
|
||||
}
|
||||
try {
|
||||
power += Integer.parseInt(att);
|
||||
} catch (final NumberFormatException nfe) {
|
||||
// can't parse the number (X for example)
|
||||
power += 0;
|
||||
}
|
||||
power += AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa, true);
|
||||
}
|
||||
if (withoutAbilities) {
|
||||
return power;
|
||||
}
|
||||
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
||||
if (!(ability instanceof AbilityActivated)) {
|
||||
continue;
|
||||
}
|
||||
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
||||
@@ -1058,7 +1041,7 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ability.hasParam("Adapt") && blocker.getCounters(CounterType.P1P1) > 0) {
|
||||
if (ability.hasParam("Adapt") && blocker.getCounters(CounterEnumType.P1P1) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1108,102 +1091,61 @@ public class ComputerUtilCombat {
|
||||
}
|
||||
theTriggers.addAll(attacker.getTriggers());
|
||||
for (final Trigger trigger : theTriggers) {
|
||||
final Map<String, String> trigParams = trigger.getMapParams();
|
||||
final Card source = trigger.getHostCard();
|
||||
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, String> abilityParams = null;
|
||||
if (trigger.getOverridingAbility() != null) {
|
||||
abilityParams = trigger.getOverridingAbility().getMapParams();
|
||||
} else if (trigParams.containsKey("Execute")) {
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
abilityParams = AbilityFactory.getMapParams(ability);
|
||||
} else {
|
||||
SpellAbility sa = trigger.ensureAbility();
|
||||
if (sa == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String abType = "";
|
||||
if (abilityParams.containsKey("AB")) {
|
||||
abType = abilityParams.get("AB");
|
||||
} else if (abilityParams.containsKey("DB")) {
|
||||
abType = abilityParams.get("DB");
|
||||
}
|
||||
|
||||
// DealDamage triggers
|
||||
if (abType.equals("DealDamage")) {
|
||||
if (!abilityParams.containsKey("Defined") || !abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
||||
continue;
|
||||
}
|
||||
int damage = 0;
|
||||
try {
|
||||
damage = Integer.parseInt(abilityParams.get("NumDmg"));
|
||||
} catch (final NumberFormatException nfe) {
|
||||
// can't parse the number (X for example)
|
||||
if (ApiType.DealDamage.equals(sa.getApi())) {
|
||||
if (!"TriggeredBlocker".equals(sa.getParam("Defined"))) {
|
||||
continue;
|
||||
}
|
||||
int damage = AbilityUtils.calculateAmount(source, sa.getParam("NumDmg"), sa);
|
||||
toughness -= predictDamageTo(blocker, damage, 0, source, false);
|
||||
continue;
|
||||
}
|
||||
} else
|
||||
|
||||
// -1/-1 PutCounter triggers
|
||||
if (abType.equals("PutCounter")) {
|
||||
if (!abilityParams.containsKey("Defined") || !abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
||||
if (ApiType.PutCounter.equals(sa.getApi())) {
|
||||
if (!"TriggeredBlocker".equals(sa.getParam("Defined"))) {
|
||||
continue;
|
||||
}
|
||||
if (!abilityParams.containsKey("CounterType") || !abilityParams.get("CounterType").equals("M1M1")) {
|
||||
if (!"M1M1".equals(sa.getParam("CounterType"))) {
|
||||
continue;
|
||||
}
|
||||
int num = 0;
|
||||
try {
|
||||
num = Integer.parseInt(abilityParams.get("CounterNum"));
|
||||
} catch (final NumberFormatException nfe) {
|
||||
// can't parse the number (X for example)
|
||||
continue;
|
||||
}
|
||||
toughness -= num;
|
||||
continue;
|
||||
}
|
||||
toughness -= AbilityUtils.calculateAmount(source, sa.getParam("CounterNum"), sa);
|
||||
} else
|
||||
|
||||
// Pump triggers
|
||||
if (!abType.equals("Pump")) {
|
||||
continue;
|
||||
}
|
||||
if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) {
|
||||
continue; // targeted pumping not supported
|
||||
}
|
||||
final List<Card> list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
|
||||
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredBlocker")) {
|
||||
list.add(blocker);
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!list.contains(blocker)) {
|
||||
continue;
|
||||
}
|
||||
if (!abilityParams.containsKey("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String def = abilityParams.get("NumDef");
|
||||
if (def.startsWith("+")) {
|
||||
def = def.substring(1);
|
||||
}
|
||||
try {
|
||||
toughness += Integer.parseInt(def);
|
||||
} catch (final NumberFormatException nfe) {
|
||||
// can't parse the number (X for example)
|
||||
if (ApiType.Pump.equals(sa.getApi())) {
|
||||
if (sa.usesTargeting()) {
|
||||
continue; // targeted pumping not supported
|
||||
}
|
||||
final List<Card> list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), null);
|
||||
if ("TriggeredBlocker".equals(sa.getParam("Defined"))) {
|
||||
list.add(blocker);
|
||||
}
|
||||
if (list.isEmpty() || !list.contains(blocker)) {
|
||||
continue;
|
||||
}
|
||||
if (!sa.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
toughness += AbilityUtils.calculateAmount(source, sa.getParam("NumDef"), sa, true);
|
||||
}
|
||||
}
|
||||
if (withoutAbilities) {
|
||||
return toughness;
|
||||
}
|
||||
for (SpellAbility ability : blocker.getAllSpellAbilities()) {
|
||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
||||
if (!(ability instanceof AbilityActivated)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1234,7 +1176,7 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ability.hasParam("Adapt") && blocker.getCounters(CounterType.P1P1) > 0) {
|
||||
if (ability.hasParam("Adapt") && blocker.getCounters(CounterEnumType.P1P1) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1296,7 +1238,7 @@ public class ComputerUtilCombat {
|
||||
if (ComputerUtilCombat.dealsFirstStrikeDamage(blocker, withoutAbilities, combat)
|
||||
&& (blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT))
|
||||
&& !ComputerUtilCombat.dealsFirstStrikeDamage(attacker, withoutAbilities, combat)
|
||||
&& !attacker.canReceiveCounters(CounterType.M1M1)) {
|
||||
&& !attacker.canReceiveCounters(CounterEnumType.M1M1)) {
|
||||
power -= blocker.getNetCombatDamage();
|
||||
}
|
||||
theTriggers.addAll(blocker.getTriggers());
|
||||
@@ -1426,7 +1368,7 @@ public class ComputerUtilCombat {
|
||||
return power;
|
||||
}
|
||||
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
|
||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
||||
if (!(ability instanceof AbilityActivated)) {
|
||||
continue;
|
||||
}
|
||||
if (ability.hasParam("ActivationPhases") || ability.hasParam("SorcerySpeed") || ability.hasParam("ActivationZone")) {
|
||||
@@ -1456,7 +1398,7 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ability.hasParam("Adapt") && attacker.getCounters(CounterType.P1P1) > 0) {
|
||||
if (ability.hasParam("Adapt") && attacker.getCounters(CounterEnumType.P1P1) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1521,148 +1463,135 @@ public class ComputerUtilCombat {
|
||||
final CardCollectionView cardList = game.getCardsIn(ZoneType.Battlefield);
|
||||
for (final Card card : cardList) {
|
||||
for (final StaticAbility stAb : card.getStaticAbilities()) {
|
||||
final Map<String, String> params = stAb.getMapParams();
|
||||
if (!params.get("Mode").equals("Continuous")) {
|
||||
if (!"Continuous".equals(stAb.getParam("Mode"))) {
|
||||
continue;
|
||||
}
|
||||
if (params.containsKey("Affected") && params.get("Affected").contains("attacking")) {
|
||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "attacking", "Creature");
|
||||
if (!stAb.hasParam("Affected")) {
|
||||
continue;
|
||||
}
|
||||
if (!stAb.hasParam("AddToughness")) {
|
||||
continue;
|
||||
}
|
||||
String affected = stAb.getParam("Affected");
|
||||
String addT = stAb.getParam("AddToughness");
|
||||
if (affected.contains("attacking")) {
|
||||
final String valid = TextUtil.fastReplace(affected, "attacking", "Creature");
|
||||
if (!attacker.isValid(valid, card.getController(), card, null)) {
|
||||
continue;
|
||||
}
|
||||
if (params.containsKey("AddToughness")) {
|
||||
if (params.get("AddToughness").equals("X")) {
|
||||
toughness += CardFactoryUtil.xCount(card, card.getSVar("X"));
|
||||
} else if (params.get("AddToughness").equals("Y")) {
|
||||
toughness += CardFactoryUtil.xCount(card, card.getSVar("Y"));
|
||||
} else {
|
||||
toughness += Integer.valueOf(params.get("AddToughness"));
|
||||
}
|
||||
}
|
||||
} else if (params.containsKey("Affected") && params.get("Affected").contains("untapped")) {
|
||||
final String valid = TextUtil.fastReplace(params.get("Affected"), "untapped", "Creature");
|
||||
toughness += AbilityUtils.calculateAmount(card, addT, stAb, true);
|
||||
} else if (affected.contains("untapped")) {
|
||||
final String valid = TextUtil.fastReplace(affected, "untapped", "Creature");
|
||||
if (!attacker.isValid(valid, card.getController(), card, null)
|
||||
|| attacker.hasKeyword(Keyword.VIGILANCE)) {
|
||||
continue;
|
||||
}
|
||||
// remove the bonus, because it will no longer be granted
|
||||
if (params.containsKey("AddToughness")) {
|
||||
toughness -= Integer.valueOf(params.get("AddToughness"));
|
||||
}
|
||||
toughness -= AbilityUtils.calculateAmount(card, addT, stAb, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final Trigger trigger : theTriggers) {
|
||||
final Map<String, String> trigParams = trigger.getMapParams();
|
||||
final Card source = trigger.getHostCard();
|
||||
|
||||
if (!ComputerUtilCombat.combatTriggerWillTrigger(attacker, blocker, trigger, combat)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, String> abilityParams = null;
|
||||
if (trigger.getOverridingAbility() != null) {
|
||||
abilityParams = trigger.getOverridingAbility().getMapParams();
|
||||
} else if (trigParams.containsKey("Execute")) {
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
abilityParams = AbilityFactory.getMapParams(ability);
|
||||
} else {
|
||||
SpellAbility sa = trigger.ensureAbility();
|
||||
if (sa == null) {
|
||||
continue;
|
||||
}
|
||||
sa.setActivatingPlayer(source.getController());
|
||||
|
||||
if (abilityParams.containsKey("ValidTgts") || abilityParams.containsKey("Tgt")) {
|
||||
if (sa.usesTargeting()) {
|
||||
continue; // targeted pumping not supported
|
||||
}
|
||||
|
||||
// DealDamage triggers
|
||||
if ((abilityParams.containsKey("AB") && abilityParams.get("AB").equals("DealDamage"))
|
||||
|| (abilityParams.containsKey("DB") && abilityParams.get("DB").equals("DealDamage"))) {
|
||||
if (!abilityParams.containsKey("Defined") || !abilityParams.get("Defined").equals("TriggeredAttacker")) {
|
||||
continue;
|
||||
}
|
||||
int damage = 0;
|
||||
try {
|
||||
damage = Integer.parseInt(abilityParams.get("NumDmg"));
|
||||
} catch (final NumberFormatException nfe) {
|
||||
// can't parse the number (X for example)
|
||||
if (ApiType.DealDamage.equals(sa.getApi())) {
|
||||
if ("TriggeredAttacker".equals(sa.getParam("Defined"))) {
|
||||
continue;
|
||||
}
|
||||
int damage = AbilityUtils.calculateAmount(source, sa.getParam("NumDmg"), sa);
|
||||
|
||||
toughness -= predictDamageTo(attacker, damage, 0, source, false);
|
||||
continue;
|
||||
}
|
||||
} else if (ApiType.Pump.equals(sa.getApi())) {
|
||||
|
||||
// Pump triggers
|
||||
if (abilityParams.containsKey("AB") && !abilityParams.get("AB").equals("Pump")
|
||||
&& !abilityParams.get("AB").equals("PumpAll")) {
|
||||
continue;
|
||||
}
|
||||
if (abilityParams.containsKey("DB") && !abilityParams.get("DB").equals("Pump")
|
||||
&& !abilityParams.get("DB").equals("PumpAll")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (abilityParams.containsKey("Cost")) {
|
||||
SpellAbility sa = null;
|
||||
if (trigger.getOverridingAbility() != null) {
|
||||
sa = trigger.getOverridingAbility();
|
||||
} else {
|
||||
final String ability = source.getSVar(trigParams.get("Execute"));
|
||||
sa = AbilityFactory.getAbility(ability, source);
|
||||
if (sa.hasParam("Cost")) {
|
||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
sa.setActivatingPlayer(source.getController());
|
||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||
if (!sa.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
List<Card> list = Lists.newArrayList();
|
||||
if (!abilityParams.containsKey("ValidCards")) {
|
||||
list = AbilityUtils.getDefinedCards(source, abilityParams.get("Defined"), null);
|
||||
}
|
||||
if (abilityParams.containsKey("Defined") && abilityParams.get("Defined").equals("TriggeredAttacker")) {
|
||||
list.add(attacker);
|
||||
}
|
||||
if (abilityParams.containsKey("ValidCards")) {
|
||||
if (attacker.isValid(abilityParams.get("ValidCards").split(","), source.getController(), source, null)
|
||||
|| attacker.isValid(abilityParams.get("ValidCards").replace("attacking+", "").split(","),
|
||||
source.getController(), source, null)) {
|
||||
CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa);
|
||||
if ("TriggeredAttacker".equals(sa.getParam("Defined"))) {
|
||||
list.add(attacker);
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!list.contains(attacker)) {
|
||||
continue;
|
||||
}
|
||||
if (!abilityParams.containsKey("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String def = abilityParams.get("NumDef");
|
||||
if (def.startsWith("+")) {
|
||||
def = def.substring(1);
|
||||
}
|
||||
if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) {
|
||||
toughness += Integer.parseInt(def);
|
||||
} else {
|
||||
String bonus = source.getSVar(def);
|
||||
if (bonus.contains("TriggerCount$NumBlockers")) {
|
||||
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
|
||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
||||
if (!list.contains(attacker)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String def = sa.getParam("NumDef");
|
||||
if (def.startsWith("+")) {
|
||||
def = def.substring(1);
|
||||
}
|
||||
if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) {
|
||||
toughness += Integer.parseInt(def);
|
||||
} else {
|
||||
String bonus = AbilityUtils.getSVar(sa, def);
|
||||
if (bonus.contains("TriggerCount$NumBlockers")) {
|
||||
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
|
||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
||||
}
|
||||
toughness += CardFactoryUtil.xCount(source, bonus);
|
||||
}
|
||||
} else if (ApiType.PumpAll.equals(sa.getApi())) {
|
||||
|
||||
if (sa.hasParam("Cost")) {
|
||||
if (!CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sa.hasParam("ValidCards")) {
|
||||
continue;
|
||||
}
|
||||
if (!sa.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
if (!attacker.isValid(sa.getParam("ValidCards").replace("attacking+", "").split(","), source.getController(), source, sa)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String def = sa.getParam("NumDef");
|
||||
if (def.startsWith("+")) {
|
||||
def = def.substring(1);
|
||||
}
|
||||
if (def.matches("[0-9][0-9]?") || def.matches("-" + "[0-9][0-9]?")) {
|
||||
toughness += Integer.parseInt(def);
|
||||
} else {
|
||||
String bonus = AbilityUtils.getSVar(sa, def);
|
||||
if (bonus.contains("TriggerCount$NumBlockers")) {
|
||||
bonus = TextUtil.fastReplace(bonus, "TriggerCount$NumBlockers", "Number$1");
|
||||
} else if (bonus.contains("TriggeredPlayersDefenders$Amount")) { // for Melee
|
||||
bonus = TextUtil.fastReplace(bonus, "TriggeredPlayersDefenders$Amount", "Number$1");
|
||||
}
|
||||
toughness += CardFactoryUtil.xCount(source, bonus);
|
||||
}
|
||||
toughness += CardFactoryUtil.xCount(source, bonus);
|
||||
}
|
||||
}
|
||||
if (withoutAbilities) {
|
||||
return toughness;
|
||||
}
|
||||
for (SpellAbility ability : attacker.getAllSpellAbilities()) {
|
||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
||||
if (!(ability instanceof AbilityActivated)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1672,18 +1601,19 @@ public class ComputerUtilCombat {
|
||||
if (ability.usesTargeting() && !ability.canTarget(attacker)) {
|
||||
continue;
|
||||
}
|
||||
if (ability.getPayCosts().hasTapCost() && !attacker.hasKeyword(Keyword.VIGILANCE)) {
|
||||
continue;
|
||||
}
|
||||
if (!ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ability.getApi() == ApiType.Pump) {
|
||||
if (!ability.hasParam("NumDef")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability);
|
||||
if (tBonus > 0) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
}
|
||||
toughness += AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("NumDef"), ability, true);
|
||||
} else if (ability.getApi() == ApiType.PutCounter) {
|
||||
if (!ability.hasParam("CounterType") || !ability.getParam("CounterType").equals("P1P1")) {
|
||||
continue;
|
||||
@@ -1693,15 +1623,13 @@ public class ComputerUtilCombat {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ability.hasParam("Adapt") && attacker.getCounters(CounterType.P1P1) > 0) {
|
||||
if (ability.hasParam("Adapt") && attacker.getCounters(CounterEnumType.P1P1) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ability.getPayCosts().hasTapCost() && ComputerUtilCost.canPayCost(ability, attacker.getController())) {
|
||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
|
||||
if (tBonus > 0) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
int tBonus = AbilityUtils.calculateAmount(ability.getHostCard(), ability.getParam("CounterNum"), ability);
|
||||
if (tBonus > 0) {
|
||||
toughness += tBonus;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1848,10 +1776,10 @@ public class ComputerUtilCombat {
|
||||
|
||||
if (((attacker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, attacker) && !withoutAbilities))
|
||||
&& !(blocker.hasKeyword(Keyword.WITHER) || blocker.hasKeyword(Keyword.INFECT)))
|
||||
|| (attacker.hasKeyword(Keyword.PERSIST) && !attacker.canReceiveCounters(CounterType.M1M1) && (attacker
|
||||
.getCounters(CounterType.M1M1) == 0))
|
||||
|| (attacker.hasKeyword(Keyword.UNDYING) && !attacker.canReceiveCounters(CounterType.P1P1) && (attacker
|
||||
.getCounters(CounterType.P1P1) == 0))) {
|
||||
|| (attacker.hasKeyword(Keyword.PERSIST) && !attacker.canReceiveCounters(CounterEnumType.M1M1) && (attacker
|
||||
.getCounters(CounterEnumType.M1M1) == 0))
|
||||
|| (attacker.hasKeyword(Keyword.UNDYING) && !attacker.canReceiveCounters(CounterEnumType.P1P1) && (attacker
|
||||
.getCounters(CounterEnumType.P1P1) == 0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2080,10 +2008,10 @@ public class ComputerUtilCombat {
|
||||
|
||||
if (((blocker.hasKeyword(Keyword.INDESTRUCTIBLE) || (ComputerUtil.canRegenerate(ai, blocker) && !withoutAbilities)) && !(attacker
|
||||
.hasKeyword(Keyword.WITHER) || attacker.hasKeyword(Keyword.INFECT)))
|
||||
|| (blocker.hasKeyword(Keyword.PERSIST) && !blocker.canReceiveCounters(CounterType.M1M1) && (blocker
|
||||
.getCounters(CounterType.M1M1) == 0))
|
||||
|| (blocker.hasKeyword(Keyword.UNDYING) && !blocker.canReceiveCounters(CounterType.P1P1) && (blocker
|
||||
.getCounters(CounterType.P1P1) == 0))) {
|
||||
|| (blocker.hasKeyword(Keyword.PERSIST) && !blocker.canReceiveCounters(CounterEnumType.M1M1) && (blocker
|
||||
.getCounters(CounterEnumType.M1M1) == 0))
|
||||
|| (blocker.hasKeyword(Keyword.UNDYING) && !blocker.canReceiveCounters(CounterEnumType.P1P1) && (blocker
|
||||
.getCounters(CounterEnumType.P1P1) == 0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2517,7 +2445,7 @@ public class ComputerUtilCombat {
|
||||
final Player controller = combatant.getController();
|
||||
for (Card c : controller.getCardsIn(ZoneType.Battlefield)) {
|
||||
for (SpellAbility ability : c.getAllSpellAbilities()) {
|
||||
if (!(ability instanceof AbilityActivated) || ability.getPayCosts() == null) {
|
||||
if (!(ability instanceof AbilityActivated)) {
|
||||
continue;
|
||||
}
|
||||
if (ability.getApi() != ApiType.Pump) {
|
||||
|
||||
@@ -45,7 +45,7 @@ public class ComputerUtilCost {
|
||||
final CostPutCounter addCounter = (CostPutCounter) part;
|
||||
final CounterType type = addCounter.getCounter();
|
||||
|
||||
if (type.equals(CounterType.M1M1)) {
|
||||
if (type.equals(CounterEnumType.M1M1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ public class ComputerUtilCost {
|
||||
|
||||
final CounterType type = remCounter.counter;
|
||||
if (!part.payCostFromSource()) {
|
||||
if (CounterType.P1P1.equals(type)) {
|
||||
if (CounterEnumType.P1P1.equals(type)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@@ -97,7 +97,7 @@ public class ComputerUtilCost {
|
||||
|
||||
// check the sa what the PaymentDecision is.
|
||||
// ignore Loyality abilities with Zero as Cost
|
||||
if (sa != null && !CounterType.LOYALTY.equals(type)) {
|
||||
if (sa != null && !CounterEnumType.LOYALTY.equals(type)) {
|
||||
final AiCostDecision decision = new AiCostDecision(sa.getActivatingPlayer(), sa);
|
||||
PaymentDecision pay = decision.visit(remCounter);
|
||||
if (pay == null || pay.c <= 0) {
|
||||
@@ -106,7 +106,7 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
//don't kill the creature
|
||||
if (CounterType.P1P1.equals(type) && source.getLethalDamage() <= 1
|
||||
if (CounterEnumType.P1P1.equals(type) && source.getLethalDamage() <= 1
|
||||
&& !source.hasKeyword(Keyword.UNDYING)) {
|
||||
return false;
|
||||
}
|
||||
@@ -467,9 +467,9 @@ public class ComputerUtilCost {
|
||||
if(!meetsRestriction)
|
||||
continue;
|
||||
|
||||
try {
|
||||
if (StringUtils.isNumeric(parts[0])) {
|
||||
extraManaNeeded += Integer.parseInt(parts[0]);
|
||||
} catch (final NumberFormatException e) {
|
||||
} else {
|
||||
System.out.println("wrong SpellsNeedExtraMana SVar format on " + c);
|
||||
}
|
||||
}
|
||||
@@ -480,9 +480,9 @@ public class ComputerUtilCost {
|
||||
}
|
||||
final String snem = c.getSVar("SpellsNeedExtraManaEffect");
|
||||
if (!StringUtils.isBlank(snem)) {
|
||||
try {
|
||||
if (StringUtils.isNumeric(snem)) {
|
||||
extraManaNeeded += Integer.parseInt(snem);
|
||||
} catch (final NumberFormatException e) {
|
||||
} else {
|
||||
System.out.println("wrong SpellsNeedExtraManaEffect SVar format on " + c);
|
||||
}
|
||||
}
|
||||
@@ -529,7 +529,7 @@ public class ComputerUtilCost {
|
||||
public boolean apply(Card card) {
|
||||
boolean hasManaSa = false;
|
||||
for (final SpellAbility sa : card.getSpellAbilities()) {
|
||||
if (sa.isManaAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.isManaAbility() && sa.getPayCosts().hasTapCost()) {
|
||||
hasManaSa = true;
|
||||
break;
|
||||
}
|
||||
@@ -619,7 +619,8 @@ public class ComputerUtilCost {
|
||||
if (combat.getAttackers().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else if ("nonToken".equals(aiLogic) && AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0).isToken()) {
|
||||
} else if ("nonToken".equals(aiLogic) && !AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).isEmpty()
|
||||
&& AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa).get(0).isToken()) {
|
||||
return false;
|
||||
} else if ("LowPriority".equals(aiLogic) && MyRandom.getRandom().nextInt(100) < 67) {
|
||||
return false;
|
||||
|
||||
@@ -120,6 +120,10 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
private static void sortManaAbilities(final Multimap<ManaCostShard, SpellAbility> manaAbilityMap) {
|
||||
sortManaAbilities(manaAbilityMap, null);
|
||||
}
|
||||
|
||||
private static void sortManaAbilities(final Multimap<ManaCostShard, SpellAbility> manaAbilityMap, final SpellAbility sa) {
|
||||
final Map<Card, Integer> manaCardMap = Maps.newHashMap();
|
||||
final List<Card> orderedCards = Lists.newArrayList();
|
||||
|
||||
@@ -185,6 +189,58 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
manaAbilityMap.replaceValues(shard, newAbilities);
|
||||
|
||||
// Sort the first N abilities so that the preferred shard is selected, e.g. Adamant
|
||||
String manaPref = sa.getParamOrDefault("AIManaPref", "");
|
||||
if (manaPref.isEmpty() && sa.getHostCard() != null && sa.getHostCard().hasSVar("AIManaPref")) {
|
||||
manaPref = sa.getHostCard().getSVar("AIManaPref");
|
||||
}
|
||||
|
||||
if (!manaPref.isEmpty()) {
|
||||
final String[] prefShardInfo = manaPref.split(":");
|
||||
final String preferredShard = prefShardInfo[0];
|
||||
final int preferredShardAmount = prefShardInfo.length > 1 ? Integer.parseInt(prefShardInfo[1]) : 3;
|
||||
|
||||
if (!preferredShard.isEmpty()) {
|
||||
final List<SpellAbility> prefSortedAbilities = new ArrayList<>(newAbilities);
|
||||
final List<SpellAbility> otherSortedAbilities = new ArrayList<>(newAbilities);
|
||||
|
||||
Collections.sort(prefSortedAbilities, new Comparator<SpellAbility>() {
|
||||
@Override
|
||||
public int compare(final SpellAbility ability1, final SpellAbility ability2) {
|
||||
if (ability1.getManaPart().mana().contains(preferredShard))
|
||||
return -1;
|
||||
else if (ability2.getManaPart().mana().contains(preferredShard))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
Collections.sort(otherSortedAbilities, new Comparator<SpellAbility>() {
|
||||
@Override
|
||||
public int compare(final SpellAbility ability1, final SpellAbility ability2) {
|
||||
if (ability1.getManaPart().mana().contains(preferredShard))
|
||||
return 1;
|
||||
else if (ability2.getManaPart().mana().contains(preferredShard))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
final List<SpellAbility> finalAbilities = new ArrayList<>();
|
||||
for (int i = 0; i < preferredShardAmount && i < prefSortedAbilities.size(); i++) {
|
||||
finalAbilities.add(prefSortedAbilities.get(i));
|
||||
}
|
||||
for (int i = 0; i < otherSortedAbilities.size(); i++) {
|
||||
SpellAbility ab = otherSortedAbilities.get(i);
|
||||
if (!finalAbilities.contains(ab))
|
||||
finalAbilities.add(ab);
|
||||
}
|
||||
|
||||
manaAbilityMap.replaceValues(shard, finalAbilities);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +366,7 @@ public class ComputerUtilMana {
|
||||
// select which abilities may be used for each shard
|
||||
Multimap<ManaCostShard, SpellAbility> sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
|
||||
|
||||
sortManaAbilities(sourcesForShards);
|
||||
sortManaAbilities(sourcesForShards, sa);
|
||||
|
||||
ManaCostShard toPay;
|
||||
// Loop over mana needed
|
||||
@@ -371,7 +427,7 @@ public class ComputerUtilMana {
|
||||
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
|
||||
List<Mana> manaSpentToPay = test ? new ArrayList<>() : sa.getPayingMana();
|
||||
boolean purePhyrexian = cost.containsOnlyPhyrexianMana();
|
||||
int testEnergyPool = ai.getCounters(CounterType.ENERGY);
|
||||
int testEnergyPool = ai.getCounters(CounterEnumType.ENERGY);
|
||||
|
||||
List<SpellAbility> paymentList = Lists.newArrayList();
|
||||
|
||||
@@ -507,16 +563,10 @@ public class ComputerUtilMana {
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (saPayment.getPayCosts() != null) {
|
||||
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
||||
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
|
||||
saList.remove(saPayment);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.err.println("Ability " + saPayment + " from " + saPayment.getHostCard() + " had NULL as payCost");
|
||||
saPayment.getHostCard().tap();
|
||||
final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment);
|
||||
if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment))) {
|
||||
saList.remove(saPayment);
|
||||
continue;
|
||||
}
|
||||
|
||||
ai.getGame().getStack().addAndUnfreeze(saPayment);
|
||||
@@ -627,7 +677,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
}
|
||||
}
|
||||
sortManaAbilities(sourcesForShards);
|
||||
sortManaAbilities(sourcesForShards, sa);
|
||||
if (DEBUG_MANA_PAYMENT) {
|
||||
System.out.println("DEBUG_MANA_PAYMENT: sourcesForShards = " + sourcesForShards);
|
||||
}
|
||||
@@ -741,7 +791,7 @@ public class ComputerUtilMana {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsManaRestrictions(saBeingPaidFor)) {
|
||||
if (thisMana.getManaAbility() != null && !thisMana.getManaAbility().meetsSpellAndShardRestrictions(saBeingPaidFor, shard, thisMana.getColor())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -837,10 +887,9 @@ public class ComputerUtilMana {
|
||||
if (checkCosts) {
|
||||
// Check if AI can still play this mana ability
|
||||
ma.setActivatingPlayer(ai);
|
||||
if (ma.getPayCosts() != null) { // if the AI can't pay the additional costs skip the mana ability
|
||||
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
|
||||
return false;
|
||||
}
|
||||
// if the AI can't pay the additional costs skip the mana ability
|
||||
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
|
||||
return false;
|
||||
}
|
||||
else if (sourceCard.isTapped()) {
|
||||
return false;
|
||||
@@ -1144,7 +1193,7 @@ public class ComputerUtilMana {
|
||||
ManaCostBeingPaid cost = new ManaCostBeingPaid(mana, restriction);
|
||||
|
||||
// Tack xMana Payments into mana here if X is a set value
|
||||
if (sa.getPayCosts() != null && (cost.getXcounter() > 0 || extraMana > 0)) {
|
||||
if (cost.getXcounter() > 0 || extraMana > 0) {
|
||||
int manaToAdd = 0;
|
||||
if (test && extraMana > 0) {
|
||||
final int multiplicator = Math.max(cost.getXcounter(), 1);
|
||||
@@ -1169,7 +1218,7 @@ public class ComputerUtilMana {
|
||||
cost.increaseShard(shardToGrow, manaToAdd);
|
||||
|
||||
if (!test) {
|
||||
card.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1218,7 +1267,7 @@ public class ComputerUtilMana {
|
||||
for (SpellAbility ma : src.getManaAbilities()) {
|
||||
ma.setActivatingPlayer(p);
|
||||
if (!checkPlayable || ma.canPlay()) {
|
||||
int costsToActivate = ma.getPayCosts() != null && ma.getPayCosts().getCostMana() != null ? ma.getPayCosts().getCostMana().convertAmount() : 0;
|
||||
int costsToActivate = ma.getPayCosts().getCostMana() != null ? ma.getPayCosts().getCostMana().convertAmount() : 0;
|
||||
int producedMana = ma.getParamOrDefault("Produced", "").split(" ").length;
|
||||
int producedAmount = AbilityUtils.calculateAmount(src, ma.getParamOrDefault("Amount", "1"), ma);
|
||||
|
||||
@@ -1594,7 +1643,7 @@ public class ComputerUtilMana {
|
||||
}
|
||||
|
||||
public static int determineMaxAffordableX(Player ai, SpellAbility sa) {
|
||||
if (sa.getPayCosts() == null || sa.getPayCosts().getCostMana() == null) {
|
||||
if (sa.getPayCosts().getCostMana() == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.google.common.base.Function;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.cost.CostPayEnergy;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.keyword.KeywordInterface;
|
||||
@@ -242,11 +242,11 @@ public class CreatureEvaluator implements Function<Card, Integer> {
|
||||
&& "+X".equals(sa.getParam("NumDef"))
|
||||
&& !sa.usesTargeting()
|
||||
&& (!sa.hasParam("Defined") || "Self".equals(sa.getParam("Defined")))) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
if (sa.getPayCosts().hasOnlySpecificCostType(CostPayEnergy.class)) {
|
||||
// Electrostatic Pummeler, can be expanded for similar cards
|
||||
int initPower = getEffectivePower(sa.getHostCard());
|
||||
int pumpedPower = initPower;
|
||||
int energy = sa.getHostCard().getController().getCounters(CounterType.ENERGY);
|
||||
int energy = sa.getHostCard().getController().getCounters(CounterEnumType.ENERGY);
|
||||
if (energy > 0) {
|
||||
int numActivations = energy / 3;
|
||||
for (int i = 0; i < numActivations; i++) {
|
||||
|
||||
@@ -592,6 +592,7 @@ public abstract class GameState {
|
||||
cardToEnchantPlayerId.clear();
|
||||
cardToRememberedId.clear();
|
||||
cardToExiledWithId.clear();
|
||||
cardToImprintedId.clear();
|
||||
markedDamage.clear();
|
||||
cardToChosenClrs.clear();
|
||||
cardToChosenCards.clear();
|
||||
@@ -1090,11 +1091,11 @@ public abstract class GameState {
|
||||
}
|
||||
|
||||
private void applyCountersToGameEntity(GameEntity entity, String counterString) {
|
||||
entity.setCounters(Maps.newEnumMap(CounterType.class));
|
||||
entity.setCounters(Maps.newHashMap());
|
||||
String[] allCounterStrings = counterString.split(",");
|
||||
for (final String counterPair : allCounterStrings) {
|
||||
String[] pair = counterPair.split("=", 2);
|
||||
entity.addCounter(CounterType.valueOf(pair[0]), Integer.parseInt(pair[1]), null, false, false, null);
|
||||
entity.addCounter(CounterType.getType(pair[0]), Integer.parseInt(pair[1]), null, false, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1136,7 +1137,7 @@ public abstract class GameState {
|
||||
Map<CounterType, Integer> counters = c.getCounters();
|
||||
// Note: Not clearCounters() since we want to keep the counters
|
||||
// var as-is.
|
||||
c.setCounters(Maps.newEnumMap(CounterType.class));
|
||||
c.setCounters(Maps.newHashMap());
|
||||
if (c.isAura()) {
|
||||
// dummy "enchanting" to indicate that the card will be force-attached elsewhere
|
||||
// (will be overridden later, so the actual value shouldn't matter)
|
||||
|
||||
@@ -145,12 +145,12 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardCollectionView chooseCardsForEffect(CardCollectionView sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional) {
|
||||
return brains.chooseCardsForEffect(sourceList, sa, min, max, isOptional);
|
||||
public CardCollectionView chooseCardsForEffect(CardCollectionView sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional, Map<String, Object> params) {
|
||||
return brains.chooseCardsForEffect(sourceList, sa, min, max, isOptional, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) {
|
||||
public <T extends GameEntity> T chooseSingleEntityForEffect(FCollectionView<T> optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
if (delayedReveal != null) {
|
||||
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
||||
}
|
||||
@@ -158,13 +158,13 @@ public class PlayerControllerAi extends PlayerController {
|
||||
if (null == api) {
|
||||
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||
}
|
||||
return SpellApiToAi.Converter.get(api).chooseSingleEntity(player, sa, (FCollection<T>)optionList, isOptional, targetedPlayer);
|
||||
return SpellApiToAi.Converter.get(api).chooseSingleEntity(player, sa, (FCollection<T>)optionList, isOptional, targetedPlayer, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GameEntity> List<T> chooseEntitiesForEffect(
|
||||
FCollectionView<T> optionList, int min, int max, DelayedReveal delayedReveal, SpellAbility sa, String title,
|
||||
Player targetedPlayer) {
|
||||
Player targetedPlayer, Map<String, Object> params) {
|
||||
if (delayedReveal != null) {
|
||||
reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix());
|
||||
}
|
||||
@@ -172,7 +172,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
List<T> selecteds = new ArrayList<>();
|
||||
T selected;
|
||||
do {
|
||||
selected = chooseSingleEntityForEffect(remaining, null, sa, title, selecteds.size()>=min, targetedPlayer);
|
||||
selected = chooseSingleEntityForEffect(remaining, null, sa, title, selecteds.size()>=min, targetedPlayer, params);
|
||||
if ( selected != null ) {
|
||||
remaining.remove(selected);
|
||||
selecteds.add(selected);
|
||||
@@ -182,7 +182,23 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellForEffect(java.util.List<SpellAbility> spells, SpellAbility sa, String title,
|
||||
public List<SpellAbility> chooseSpellAbilitiesForEffect(List<SpellAbility> spells, SpellAbility sa, String title,
|
||||
int num, Map<String, Object> params) {
|
||||
List<SpellAbility> remaining = Lists.newArrayList(spells);
|
||||
List<SpellAbility> selecteds = Lists.newArrayList();
|
||||
SpellAbility selected;
|
||||
do {
|
||||
selected = chooseSingleSpellForEffect(remaining, sa, title, params);
|
||||
if ( selected != null ) {
|
||||
remaining.remove(selected);
|
||||
selecteds.add(selected);
|
||||
}
|
||||
} while ( (selected != null ) && (selecteds.size() < num) );
|
||||
return selecteds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellAbility chooseSingleSpellForEffect(List<SpellAbility> spells, SpellAbility sa, String title,
|
||||
Map<String, Object> params) {
|
||||
ApiType api = sa.getApi();
|
||||
if (null == api) {
|
||||
@@ -208,15 +224,13 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmTrigger(WrappedAbility wrapper, Map<String, String> triggerParams, boolean isMandatory) {
|
||||
public boolean confirmTrigger(WrappedAbility wrapper) {
|
||||
final SpellAbility sa = wrapper.getWrappedAbility();
|
||||
//final Trigger regtrig = wrapper.getTrigger();
|
||||
if (ComputerUtilAbility.getAbilitySourceName(sa).equals("Deathmist Raptor")) {
|
||||
return true;
|
||||
}
|
||||
if (triggerParams.containsKey("DelayedTrigger") || isMandatory) {
|
||||
//TODO: The only card with an optional delayed trigger is Shirei, Shizo's Caretaker,
|
||||
// needs to be expanded when a more difficult cards comes up
|
||||
if (wrapper.isMandatory()) {
|
||||
return true;
|
||||
}
|
||||
// Store/replace target choices more properly to get this SA cleared.
|
||||
@@ -503,7 +517,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
|
||||
@Override
|
||||
public String chooseSomeType(String kindOfType, SpellAbility sa, Collection<String> validTypes, List<String> invalidTypes, boolean isOptional) {
|
||||
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), invalidTypes);
|
||||
String chosen = ComputerUtil.chooseSomeType(player, kindOfType, sa.getParam("AILogic"), validTypes, invalidTypes);
|
||||
if (StringUtils.isBlank(chosen) && !validTypes.isEmpty()) {
|
||||
chosen = validTypes.iterator().next();
|
||||
System.err.println("AI has no idea how to choose " + kindOfType +", defaulting to arbitrary element: chosen");
|
||||
@@ -603,6 +617,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
if (sa instanceof LandAbility) {
|
||||
if (sa.canPlay()) {
|
||||
sa.resolve();
|
||||
game.updateLastStateForCard(sa.getHostCard());
|
||||
}
|
||||
} else {
|
||||
ComputerUtil.handlePlayingSpellAbility(player, sa, game);
|
||||
@@ -754,6 +769,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return defaultVal != null && defaultVal.booleanValue();
|
||||
case UntapTimeVault: return false; // TODO Should AI skip his turn for time vault?
|
||||
case LeftOrRight: return brains.chooseDirection(sa);
|
||||
case OddsOrEvens: return brains.chooseEvenOdd(sa); // false is Odd, true is Even
|
||||
default:
|
||||
return MyRandom.getRandom().nextBoolean();
|
||||
}
|
||||
@@ -1122,7 +1138,8 @@ public class PlayerControllerAi extends PlayerController {
|
||||
CardCollectionView cards = CardLists.getValidCards(aiLibrary, "Creature", player, sa.getHostCard());
|
||||
return ComputerUtilCard.getMostProminentCardName(cards);
|
||||
} else if (logic.equals("BestCreatureInComputerDeck")) {
|
||||
return ComputerUtilCard.getBestCreatureAI(aiLibrary).getName();
|
||||
Card bestCreature = ComputerUtilCard.getBestCreatureAI(aiLibrary);
|
||||
return bestCreature != null ? bestCreature.getName() : "Plains";
|
||||
} else if (logic.equals("RandomInComputerDeck")) {
|
||||
return Aggregates.random(aiLibrary).getName();
|
||||
} else if (logic.equals("MostProminentSpellInComputerDeck")) {
|
||||
@@ -1213,7 +1230,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
public List<OptionalCostValue> chooseOptionalCosts(SpellAbility chosen,
|
||||
List<OptionalCostValue> optionalCostValues) {
|
||||
List<OptionalCostValue> chosenOptCosts = Lists.newArrayList();
|
||||
Cost costSoFar = chosen.getPayCosts() != null ? chosen.getPayCosts().copy() : Cost.Zero;
|
||||
Cost costSoFar = chosen.getPayCosts().copy();
|
||||
|
||||
for (OptionalCostValue opt : optionalCostValues) {
|
||||
// Choose the optional cost if it can be paid (to be improved later, check for playability and other conditions perhaps)
|
||||
@@ -1252,7 +1269,7 @@ public class PlayerControllerAi extends PlayerController {
|
||||
// TODO: improve the logic depending on the keyword and the playability of the cost-modified SA (enough targets present etc.)
|
||||
int chosenAmount = 0;
|
||||
|
||||
Cost costSoFar = sa.getPayCosts() != null ? sa.getPayCosts().copy() : Cost.Zero;
|
||||
Cost costSoFar = sa.getPayCosts().copy();
|
||||
|
||||
for (int i = 0; i < max; i++) {
|
||||
costSoFar.add(cost);
|
||||
@@ -1268,13 +1285,16 @@ public class PlayerControllerAi extends PlayerController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardCollection chooseCardsForEffectMultiple(Map<String, CardCollection> validMap, SpellAbility sa, String title) {
|
||||
public CardCollection chooseCardsForEffectMultiple(Map<String, CardCollection> validMap, SpellAbility sa, String title, boolean isOptional) {
|
||||
CardCollection choices = new CardCollection();
|
||||
|
||||
for (String mapKey: validMap.keySet()) {
|
||||
CardCollection cc = validMap.get(mapKey);
|
||||
cc.removeAll(choices);
|
||||
choices.add(ComputerUtilCard.getBestAI(cc)); // TODO: should the AI limit itself here with the max number of cards in hand?
|
||||
Card chosen = ComputerUtilCard.getBestAI(cc);
|
||||
if (chosen != null) {
|
||||
choices.add(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
return choices;
|
||||
|
||||
341
forge-ai/src/main/java/forge/ai/SpecialAiLogic.java
Normal file
341
forge-ai/src/main/java/forge/ai/SpecialAiLogic.java
Normal file
@@ -0,0 +1,341 @@
|
||||
package forge.ai;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import forge.ai.ability.TokenAi;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.keyword.Keyword;
|
||||
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;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
/*
|
||||
* This class contains logic which is shared by several cards with different ability types (e.g. AF ChangeZone / AF Destroy)
|
||||
* Ideally, the naming scheme for methods in this class should be doXXXLogic, where XXX is the name of the logic,
|
||||
* and the signature of the method should be "public static boolean doXXXLogic(final Player ai, final SpellAbility sa),
|
||||
* possibly followed with any additional necessary parameters. These AI logic routines generally do all the work, so returning
|
||||
* true from them should indicate that the AI has made a decision and configured the spell ability (targeting, etc.) as it
|
||||
* deemed necessary.
|
||||
*/
|
||||
|
||||
public class SpecialAiLogic {
|
||||
// A logic for cards like Pongify, Crib Swap, Angelic Ascension
|
||||
public static boolean doPongifyLogic(final Player ai, final SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
Game game = source.getGame();
|
||||
PhaseHandler ph = game.getPhaseHandler();
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
|
||||
CardCollection listOpp = CardLists.getValidCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa);
|
||||
listOpp = CardLists.getTargetableCards(listOpp, sa);
|
||||
|
||||
Card choice = ComputerUtilCard.getMostExpensivePermanentAI(listOpp);
|
||||
|
||||
final Card token = choice != null ? TokenAi.spawnToken(choice.getController(), sa.getSubAbility()) : null;
|
||||
if (token == null || !token.isCreature() || token.getNetToughness() < 1) {
|
||||
return true; // becomes Terminate
|
||||
} else if (choice != null && choice.isPlaneswalker()) {
|
||||
if (choice.getCurrentLoyalty() * 35 > ComputerUtilCard.evaluateCreature(token)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(choice);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
boolean hasOppTarget = true;
|
||||
if (choice != null
|
||||
&& ((!choice.isCreature() || choice.isTapped()) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.getPlayerTurn() == ai) // prevent surprise combatant
|
||||
|| ComputerUtilCard.evaluateCreature(choice) < 1.5 * ComputerUtilCard.evaluateCreature(token)) {
|
||||
|
||||
hasOppTarget = false;
|
||||
}
|
||||
|
||||
// See if we have anything we can upgrade
|
||||
if (!hasOppTarget) {
|
||||
CardCollection listOwn = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa);
|
||||
listOwn = CardLists.getTargetableCards(listOwn, sa);
|
||||
|
||||
Card bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return card.isCreature() && (ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| ComputerUtilCard.evaluateCreature(token) > 2 * ComputerUtilCard.evaluateCreature(card));
|
||||
}
|
||||
}));
|
||||
if (bestOwnCardToUpgrade != null) {
|
||||
if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || ph.getPlayerTurn() != ai)) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(bestOwnCardToUpgrade);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(choice);
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasOppTarget;
|
||||
}
|
||||
}
|
||||
|
||||
// A logic for cards that say "Sacrifice a creature: CARDNAME gets +X/+X until EOT"
|
||||
public static boolean doAristocratLogic(final Player ai, final SpellAbility sa) {
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final Card source = sa.getHostCard();
|
||||
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
|
||||
final int powerBonus = sa.hasParam("NumAtt") ? AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa) : 0;
|
||||
final int toughnessBonus = sa.hasParam("NumDef") ? AbilityUtils.calculateAmount(source, sa.getParam("NumDef"), sa) : 0;
|
||||
final boolean indestructible = sa.hasParam("KW") && sa.getParam("KW").contains("Indestructible");
|
||||
final int selfEval = ComputerUtilCard.evaluateCreature(source);
|
||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
|
||||
|
||||
if (numOtherCreats == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to save the card from death by pumping it if it's threatened with a damage spell
|
||||
if (isThreatened && (toughnessBonus > 0 || indestructible)) {
|
||||
SpellAbility saTop = game.getStack().peekAbility();
|
||||
|
||||
if (saTop.getApi() == ApiType.DealDamage || saTop.getApi() == ApiType.DamageAll) {
|
||||
int dmg = AbilityUtils.calculateAmount(saTop.getHostCard(), saTop.getParam("NumDmg"), saTop) + source.getDamage();
|
||||
final int numCreatsToSac = indestructible ? 1 : Math.max(1, (int)Math.ceil((dmg - source.getNetToughness() + 1) / toughnessBonus));
|
||||
|
||||
if (numCreatsToSac > 1) { // probably not worth sacrificing too much
|
||||
return false;
|
||||
}
|
||||
|
||||
if (indestructible || (source.getNetToughness() <= dmg && source.getNetToughness() + toughnessBonus * numCreatsToSac > dmg)) {
|
||||
final CardCollection sacFodder = CardLists.filter(ai.getCreaturesInPlay(),
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| card.hasSVar("SacMe")
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval; // Maybe around 150 is OK?
|
||||
}
|
||||
}
|
||||
);
|
||||
return sacFodder.size() >= numCreatsToSac;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (combat == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (combat.isAttacking(source)) {
|
||||
if (combat.getBlockers(source).isEmpty()) {
|
||||
// Unblocked. Check if able to deal lethal, then sac'ing everything is fair game if
|
||||
// the opponent is tapped out or if we're willing to risk it (will currently risk it
|
||||
// in case it sacs less than half its creatures to deal lethal damage)
|
||||
|
||||
// TODO: also teach the AI to account for Trample, but that's trickier (needs to account fully
|
||||
// for potential damage prevention, various effects like reducing damage to 0, etc.)
|
||||
|
||||
final Player defPlayer = combat.getDefendingPlayerRelatedTo(source);
|
||||
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;
|
||||
|
||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
|
||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||
|
||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||
}
|
||||
|
||||
final int numCreatsToSac = indestructible ? 1 : (lethalDmg - source.getNetCombatDamage()) / (powerBonus != 0 ? powerBonus : 1);
|
||||
|
||||
if (defTappedOut || numCreatsToSac < numOtherCreats / 2) {
|
||||
return source.getNetCombatDamage() < lethalDmg
|
||||
&& source.getNetCombatDamage() + numOtherCreats * powerBonus >= lethalDmg;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
|
||||
// than the card we attacked with.
|
||||
final CardCollection sacTgts = CardLists.filter(ai.getCreaturesInPlay(),
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (sacTgts.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness);
|
||||
final int DefP = indestructible ? 0 : Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
|
||||
|
||||
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
|
||||
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
|
||||
}
|
||||
} else {
|
||||
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
|
||||
final CardCollection sacFodder = CardLists.filter(ai.getCreaturesInPlay(),
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| card.hasSVar("SacMe")
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval; // Maybe around 150 is OK?
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return !sacFodder.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
// A logic for cards that say "Sacrifice a creature: put X +1/+1 counters on CARDNAME" (e.g. Falkenrath Aristocrat)
|
||||
public static boolean doAristocratWithCountersLogic(final Player ai, final SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final String logic = sa.getParam("AILogic"); // should not even get here unless there's an Aristocrats logic applied
|
||||
final boolean isDeclareBlockers = ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS);
|
||||
|
||||
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
|
||||
if (numOtherCreats == 0) {
|
||||
// Cut short if there's nothing to sac at all
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the standard Aristocrats logic applies first (if in the right conditions for it)
|
||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
|
||||
if (isDeclareBlockers || isThreatened) {
|
||||
if (doAristocratLogic(ai, sa)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if anything is to be gained from the PutCounter subability
|
||||
SpellAbility countersSa = null;
|
||||
if (sa.getSubAbility() == null || sa.getSubAbility().getApi() != ApiType.PutCounter) {
|
||||
if (sa.getApi() == ApiType.PutCounter) {
|
||||
// called directly from CountersPutAi
|
||||
countersSa = sa;
|
||||
}
|
||||
} else {
|
||||
countersSa = sa.getSubAbility();
|
||||
}
|
||||
|
||||
if (countersSa == null) {
|
||||
// Shouldn't get here if there is no PutCounter subability (wrong AI logic specified?)
|
||||
System.err.println("Warning: AILogic AristocratCounters was specified on " + source + ", but there was no PutCounter SA in chain!");
|
||||
return false;
|
||||
}
|
||||
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final int selfEval = ComputerUtilCard.evaluateCreature(source);
|
||||
|
||||
String typeToGainCtr = "";
|
||||
if (logic.contains(".")) {
|
||||
typeToGainCtr = logic.substring(logic.indexOf(".") + 1);
|
||||
}
|
||||
CardCollection relevantCreats = typeToGainCtr.isEmpty() ? ai.getCreaturesInPlay()
|
||||
: CardLists.filter(ai.getCreaturesInPlay(), CardPredicates.isType(typeToGainCtr));
|
||||
relevantCreats.remove(source);
|
||||
if (relevantCreats.isEmpty()) {
|
||||
// No relevant creatures to sac
|
||||
return false;
|
||||
}
|
||||
|
||||
int numCtrs = AbilityUtils.calculateAmount(source, countersSa.getParam("CounterNum"), countersSa);
|
||||
|
||||
if (combat != null && combat.isAttacking(source) && isDeclareBlockers) {
|
||||
if (combat.getBlockers(source).isEmpty()) {
|
||||
// Unblocked. Check if we can deal lethal after receiving counters.
|
||||
final Player defPlayer = combat.getDefendingPlayerRelatedTo(source);
|
||||
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;
|
||||
|
||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
|
||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||
|
||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||
}
|
||||
|
||||
// Check if there's anything that will die anyway that can be eaten to gain a perma-bonus
|
||||
final CardCollection forcedSacTgts = CardLists.filter(relevantCreats,
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|
||||
|| (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, card, combat));
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!forcedSacTgts.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final int numCreatsToSac = Math.max(0, (lethalDmg - source.getNetCombatDamage()) / numCtrs);
|
||||
|
||||
if (defTappedOut || numCreatsToSac < relevantCreats.size() / 2) {
|
||||
return source.getNetCombatDamage() < lethalDmg
|
||||
&& source.getNetCombatDamage() + relevantCreats.size() * numCtrs >= lethalDmg;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
|
||||
// than the card we attacked with. Since we're getting a permanent bonus, consider sacrificing
|
||||
// things that are also threatened to be destroyed anyway.
|
||||
final CardCollection sacTgts = CardLists.filter(relevantCreats,
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval
|
||||
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (sacTgts.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean sourceCantDie = ComputerUtilCombat.attackerCantBeDestroyedInCombat(ai, source);
|
||||
final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness);
|
||||
final int DefP = sourceCantDie ? 0 : Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
|
||||
|
||||
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
|
||||
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
|
||||
}
|
||||
} else {
|
||||
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
|
||||
final boolean isBlocking = combat != null && combat.isBlocking(source);
|
||||
final CardCollection sacFodder = CardLists.filter(relevantCreats,
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| card.hasSVar("SacMe")
|
||||
|| (isBlocking && ComputerUtilCard.evaluateCreature(card) < selfEval)
|
||||
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return !sacFodder.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,7 +327,7 @@ public class SpecialCardAi {
|
||||
boolean canTrample = source.hasKeyword(Keyword.TRAMPLE);
|
||||
|
||||
if (!isBlocking && combat.getDefenderByAttacker(source) instanceof Card) {
|
||||
int loyalty = combat.getDefenderByAttacker(source).getCounters(CounterType.LOYALTY);
|
||||
int loyalty = combat.getDefenderByAttacker(source).getCounters(CounterEnumType.LOYALTY);
|
||||
int totalDamageToPW = 0;
|
||||
for (Card atk : (combat.getAttackersOf(combat.getDefenderByAttacker(source)))) {
|
||||
if (combat.isUnblocked(atk)) {
|
||||
@@ -407,7 +407,7 @@ public class SpecialCardAi {
|
||||
}
|
||||
|
||||
public static Pair<Integer, Integer> getPumpedPT(Player ai, int power, int toughness) {
|
||||
int energy = ai.getCounters(CounterType.ENERGY);
|
||||
int energy = ai.getCounters(CounterEnumType.ENERGY);
|
||||
if (energy > 0) {
|
||||
int numActivations = energy / 3;
|
||||
for (int i = 0; i < numActivations; i++) {
|
||||
@@ -708,7 +708,7 @@ public class SpecialCardAi {
|
||||
// if there's another reanimator card currently suspended, don't cast a new one until the previous
|
||||
// one resolves, otherwise the reanimation attempt will be ruined (e.g. Living End)
|
||||
for (Card ex : ai.getCardsIn(ZoneType.Exile)) {
|
||||
if (ex.hasSVar("IsReanimatorCard") && ex.getCounters(CounterType.TIME) > 0) {
|
||||
if (ex.hasSVar("IsReanimatorCard") && ex.getCounters(CounterEnumType.TIME) > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -756,6 +756,37 @@ public class SpecialCardAi {
|
||||
}
|
||||
}
|
||||
|
||||
// Maze's End
|
||||
public static class MazesEnd {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
CardCollection availableGates = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.isType("Gate"));
|
||||
|
||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai && !availableGates.isEmpty();
|
||||
}
|
||||
|
||||
public static Card considerCardToGet(final Player ai, final SpellAbility sa)
|
||||
{
|
||||
CardCollection currentGates = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.isType("Gate"));
|
||||
CardCollection availableGates = CardLists.filter(ai.getCardsIn(ZoneType.Library), CardPredicates.isType("Gate"));
|
||||
|
||||
if (availableGates.isEmpty())
|
||||
return null; // shouldn't get here
|
||||
|
||||
for (Card gate : availableGates)
|
||||
{
|
||||
if (CardLists.filter(currentGates, CardPredicates.nameEquals(gate.getName())).isEmpty())
|
||||
{
|
||||
// Diversify our mana base
|
||||
return gate;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch a random gate if we already have all types
|
||||
return Aggregates.random(availableGates);
|
||||
}
|
||||
}
|
||||
|
||||
// Mairsil, the Pretender
|
||||
public static class MairsilThePretender {
|
||||
// Scan the fetch list for a card with at least one activated ability.
|
||||
@@ -767,7 +798,7 @@ public class SpecialCardAi {
|
||||
Player controller = c.getController();
|
||||
boolean wasCaged = false;
|
||||
for (Card caged : CardLists.filter(controller.getCardsIn(ZoneType.Exile),
|
||||
CardPredicates.hasCounter(CounterType.CAGE))) {
|
||||
CardPredicates.hasCounter(CounterEnumType.CAGE))) {
|
||||
if (c.getName().equals(caged.getName())) {
|
||||
wasCaged = true;
|
||||
break;
|
||||
@@ -1073,7 +1104,7 @@ public class SpecialCardAi {
|
||||
// Sarkhan the Mad
|
||||
public static class SarkhanTheMad {
|
||||
public static boolean considerDig(final Player ai, final SpellAbility sa) {
|
||||
return sa.getHostCard().getCounters(CounterType.LOYALTY) == 1;
|
||||
return sa.getHostCard().getCounters(CounterEnumType.LOYALTY) == 1;
|
||||
}
|
||||
|
||||
public static boolean considerMakeDragon(final Player ai, final SpellAbility sa) {
|
||||
@@ -1109,7 +1140,7 @@ public class SpecialCardAi {
|
||||
// Sorin, Vengeful Bloodlord
|
||||
public static class SorinVengefulBloodlord {
|
||||
public static boolean consider(final Player ai, final SpellAbility sa) {
|
||||
int loyalty = sa.getHostCard().getCounters(CounterType.LOYALTY);
|
||||
int loyalty = sa.getHostCard().getCounters(CounterEnumType.LOYALTY);
|
||||
CardCollection creaturesToGet = CardLists.filter(ai.getCardsIn(ZoneType.Graveyard),
|
||||
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.lessCMC(loyalty - 1), new Predicate<Card>() {
|
||||
@Override
|
||||
@@ -1365,7 +1396,7 @@ public class SpecialCardAi {
|
||||
Card source = sa.getHostCard();
|
||||
Game game = source.getGame();
|
||||
|
||||
final int loyalty = source.getCounters(CounterType.LOYALTY);
|
||||
final int loyalty = source.getCounters(CounterEnumType.LOYALTY);
|
||||
int x = -1, best = 0;
|
||||
Card single = null;
|
||||
for (int i = 0; i < loyalty; i++) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.ICardFace;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
@@ -167,7 +168,8 @@ public abstract class SpellAbilityAi {
|
||||
|
||||
// a mandatory SpellAbility with targeting but without candidates,
|
||||
// does not need to go any deeper
|
||||
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||
if (sa.usesTargeting() && mandatory && !sa.isTargetNumberValid()
|
||||
&& !sa.getTargetRestrictions().hasCandidates(sa, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -247,6 +249,7 @@ public abstract class SpellAbilityAi {
|
||||
protected static boolean isSorcerySpeed(final SpellAbility sa) {
|
||||
return (sa.getRootAbility().isSpell() && sa.getHostCard().isSorcery())
|
||||
|| (sa.getRootAbility().isAbility() && sa.getRestrictions().isSorcerySpeed())
|
||||
|| (sa.getRootAbility().isAdventure() && sa.getHostCard().getState(CardStateName.Adventure).getType().isSorcery())
|
||||
|| (sa.isPwAbility() && !sa.getHostCard().hasKeyword("CARDNAME's loyalty abilities can be activated at instant speed."));
|
||||
}
|
||||
|
||||
@@ -264,7 +267,7 @@ public abstract class SpellAbilityAi {
|
||||
|
||||
// TODO probably also consider if winter orb or similar are out
|
||||
|
||||
if (sa.getPayCosts() == null || sa instanceof AbilitySub) {
|
||||
if (sa instanceof AbilitySub) {
|
||||
return true; // This is only true for Drawbacks and triggers
|
||||
}
|
||||
|
||||
@@ -304,7 +307,7 @@ public abstract class SpellAbilityAi {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
boolean hasPlayer = false;
|
||||
boolean hasCard = false;
|
||||
boolean hasPlaneswalker = false;
|
||||
@@ -321,11 +324,11 @@ public abstract class SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (hasPlayer && hasPlaneswalker) {
|
||||
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options);
|
||||
return (T) chooseSinglePlayerOrPlaneswalker(ai, sa, (Collection<GameEntity>) options, params);
|
||||
} else if (hasCard) {
|
||||
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer);
|
||||
return (T) chooseSingleCard(ai, sa, (Collection<Card>) options, isOptional, targetedPlayer, params);
|
||||
} else if (hasPlayer) {
|
||||
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options);
|
||||
return (T) chooseSinglePlayer(ai, sa, (Collection<Player>) options, params);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -336,17 +339,17 @@ public abstract class SpellAbilityAi {
|
||||
return spells.get(0);
|
||||
}
|
||||
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSingleCard is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayer is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseSinglePlayerOrPlaneswalker is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.BidLife, BidLifeAi.class)
|
||||
.put(ApiType.Bond, BondAi.class)
|
||||
.put(ApiType.Branch, AlwaysPlayAi.class)
|
||||
.put(ApiType.ChangeCombatants, CannotPlayAi.class)
|
||||
.put(ApiType.ChangeCombatants, ChangeCombatantsAi.class)
|
||||
.put(ApiType.ChangeTargets, ChangeTargetsAi.class)
|
||||
.put(ApiType.ChangeX, AlwaysPlayAi.class)
|
||||
.put(ApiType.ChangeZone, ChangeZoneAi.class)
|
||||
@@ -42,6 +42,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.ChooseCard, ChooseCardAi.class)
|
||||
.put(ApiType.ChooseColor, ChooseColorAi.class)
|
||||
.put(ApiType.ChooseDirection, ChooseDirectionAi.class)
|
||||
.put(ApiType.ChooseEvenOdd, ChooseEvenOddAi.class)
|
||||
.put(ApiType.ChooseNumber, ChooseNumberAi.class)
|
||||
.put(ApiType.ChoosePlayer, ChoosePlayerAi.class)
|
||||
.put(ApiType.ChooseSource, ChooseSourceAi.class)
|
||||
@@ -83,7 +84,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.FlipACoin, FlipACoinAi.class)
|
||||
.put(ApiType.Fog, FogAi.class)
|
||||
.put(ApiType.GainControl, ControlGainAi.class)
|
||||
.put(ApiType.GainControlVariant, AlwaysPlayAi.class)
|
||||
.put(ApiType.GainControlVariant, ControlGainVariantAi.class)
|
||||
.put(ApiType.GainLife, LifeGainAi.class)
|
||||
.put(ApiType.GainOwnership, CannotPlayAi.class)
|
||||
.put(ApiType.GameDrawn, CannotPlayAi.class)
|
||||
@@ -91,6 +92,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.Goad, GoadAi.class)
|
||||
.put(ApiType.Haunt, HauntAi.class)
|
||||
.put(ApiType.ImmediateTrigger, AlwaysPlayAi.class)
|
||||
.put(ApiType.Investigate, InvestigateAi.class)
|
||||
.put(ApiType.LoseLife, LifeLoseAi.class)
|
||||
.put(ApiType.LosesGame, GameLossAi.class)
|
||||
.put(ApiType.Mana, ManaEffectAi.class)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
@@ -23,12 +25,12 @@ public class AmassAi extends SpellAbilityAi {
|
||||
final Game game = ai.getGame();
|
||||
|
||||
if (!aiArmies.isEmpty()) {
|
||||
return CardLists.count(aiArmies, CardPredicates.canReceiveCounters(CounterType.P1P1)) > 0;
|
||||
return CardLists.count(aiArmies, CardPredicates.canReceiveCounters(CounterEnumType.P1P1)) > 0;
|
||||
} else {
|
||||
final String tokenScript = "b_0_0_zombie_army";
|
||||
final int amount = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("Num", "1"), sa);
|
||||
|
||||
Card token = TokenInfo.getProtoType(tokenScript, sa);
|
||||
Card token = TokenInfo.getProtoType(tokenScript, sa, false);
|
||||
|
||||
if (token == null) {
|
||||
return false;
|
||||
@@ -44,8 +46,8 @@ public class AmassAi extends SpellAbilityAi {
|
||||
CardCollection preList = new CardCollection(token);
|
||||
game.getAction().checkStaticAbilities(false, Sets.newHashSet(token), preList);
|
||||
|
||||
if (token.canReceiveCounters(CounterType.P1P1)) {
|
||||
token.setCounters(CounterType.P1P1, amount);
|
||||
if (token.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||
token.setCounters(CounterEnumType.P1P1, amount);
|
||||
}
|
||||
|
||||
if (token.isCreature() && token.getNetToughness() < 1) {
|
||||
@@ -86,8 +88,8 @@ public class AmassAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
Iterable<Card> better = CardLists.filter(options, CardPredicates.canReceiveCounters(CounterType.P1P1));
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
Iterable<Card> better = CardLists.filter(options, CardPredicates.canReceiveCounters(CounterEnumType.P1P1));
|
||||
if (Iterables.isEmpty(better)) {
|
||||
better = options;
|
||||
}
|
||||
|
||||
@@ -247,7 +247,6 @@ public class AnimateAi extends SpellAbilityAi {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
final boolean alwaysActivatePWAbility = sa.hasParam("Planeswalker")
|
||||
&& sa.getPayCosts() != null
|
||||
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class)
|
||||
&& sa.getTargetRestrictions() != null
|
||||
&& sa.getTargetRestrictions().getMinTargets(sa.getHostCard(), sa) == 0;
|
||||
|
||||
@@ -69,11 +69,6 @@ public class AttachAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DECLARE_BLOCKERS)
|
||||
&& !"Curse".equals(sa.getParam("AILogic"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent run-away activations - first time will always return true
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false;
|
||||
@@ -398,7 +393,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if (!c.isCreature() && !c.getType().hasSubtype("Vehicle") && !c.isTapped()) {
|
||||
// try to identify if this thing can actually tap
|
||||
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
||||
if (ab.getPayCosts() != null && ab.getPayCosts().hasTapCost()) {
|
||||
if (ab.getPayCosts().hasTapCost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -560,7 +555,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -838,7 +833,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
* @return the card
|
||||
*/
|
||||
private static Card attachAICursePreference(final SpellAbility sa, final List<Card> list, final boolean mandatory,
|
||||
final Card attachSource) {
|
||||
final Card attachSource, final Player ai) {
|
||||
// AI For choosing a Card to Curse of.
|
||||
|
||||
// TODO Figure out some way to combine The "gathering of data" from
|
||||
@@ -930,6 +925,24 @@ public class AttachAi extends SpellAbilityAi {
|
||||
);
|
||||
}
|
||||
|
||||
// If this is already attached and there's a sac cost, make sure we attach to something that's
|
||||
// seriously better than whatever the attachment is currently attached to (e.g. Bound by Moonsilver)
|
||||
if (sa.getHostCard().getAttachedTo() != null && sa.getHostCard().getAttachedTo().isCreature()
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||
final int oldEvalRating = ComputerUtilCard.evaluateCreature(sa.getHostCard().getAttachedTo());
|
||||
final int threshold = ai.isAI() ? ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD) : Integer.MAX_VALUE;
|
||||
prefList = CardLists.filter(prefList, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
if (!card.isCreature()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ComputerUtilCard.evaluateCreature(card) >= oldEvalRating + threshold;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
c = ComputerUtilCard.getBestAI(prefList);
|
||||
|
||||
if (c == null) {
|
||||
@@ -1455,7 +1468,7 @@ public class AttachAi extends SpellAbilityAi {
|
||||
if ("GainControl".equals(logic)) {
|
||||
c = attachAIControlPreference(sa, prefList, mandatory, attachSource);
|
||||
} else if ("Curse".equals(logic)) {
|
||||
c = attachAICursePreference(sa, prefList, mandatory, attachSource);
|
||||
c = attachAICursePreference(sa, prefList, mandatory, attachSource, ai);
|
||||
} else if ("Pump".equals(logic)) {
|
||||
c = attachAIPumpPreference(ai, sa, prefList, mandatory, attachSource);
|
||||
} else if ("Curiosity".equals(logic)) {
|
||||
@@ -1704,12 +1717,12 @@ public class AttachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
return attachToCardAIPreferences(ai, sa, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
return attachToPlayerAIPreferences(ai, sa, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
@@ -50,7 +52,7 @@ public final class BondAi extends SpellAbilityAi {
|
||||
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
return ComputerUtilCard.getBestCreatureAI(options);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerPredicates;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChangeCombatantsAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
// TODO: Extend this if possible for cards that have this as an activated ability
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(aiPlayer, sa);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#chkAIDrawback(java.util.Map, forge.card.spellability.SpellAbility, forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
final String logic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
if (logic.equals("WeakestOppExceptCtrl")) {
|
||||
PlayerCollection targetableOpps = aiPlayer.getOpponents();
|
||||
targetableOpps.remove(sa.getHostCard().getController());
|
||||
if (targetableOpps.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
PlayerCollection targetableOpps = new PlayerCollection();
|
||||
for (GameEntity p : options) {
|
||||
if (p instanceof Player && !p.equals(sa.getHostCard().getController())) {
|
||||
Player pp = (Player)p;
|
||||
if (pp.isOpponentOf(ai)) {
|
||||
targetableOpps.add(pp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Player weakestTargetableOpp = targetableOpps.filter(PlayerPredicates.isTargetableBy(sa))
|
||||
.min(PlayerPredicates.compareByLife());
|
||||
|
||||
return (T)weakestTargetableOpp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (aiLogic.equals("Pongify")) {
|
||||
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
||||
}
|
||||
|
||||
return super.checkAiLogic(ai, sa, aiLogic);
|
||||
@@ -128,6 +130,10 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
// This logic only fills the multiple cards array, the decision to play is made
|
||||
// separately in hiddenOriginCanPlayAI later.
|
||||
multipleCardsToChoose = SpecialCardAi.Intuition.considerMultiple(aiPlayer, sa);
|
||||
} else if (aiLogic.equals("MazesEnd")) {
|
||||
return SpecialCardAi.MazesEnd.consider(aiPlayer, sa);
|
||||
} else if (aiLogic.equals("Pongify")) {
|
||||
return sa.isTargetNumberValid(); // Pre-targeted in checkAiLogic
|
||||
}
|
||||
}
|
||||
if (isHidden(sa)) {
|
||||
@@ -371,9 +377,9 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
if ("Atarka's Command".equals(sourceName)
|
||||
&& (list.size() < 2 || ai.getLandsPlayedThisTurn() < 1)) {
|
||||
// be strict on playing lands off charms
|
||||
return false;
|
||||
&& (list.size() < 2 || ai.getLandsPlayedThisTurn() < 1)) {
|
||||
// be strict on playing lands off charms
|
||||
return false;
|
||||
}
|
||||
|
||||
String num = sa.getParam("ChangeNum");
|
||||
@@ -402,7 +408,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// don't use fetching to top of library/graveyard before main2
|
||||
@@ -418,7 +424,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
final AbilitySub subAb = sa.getSubAbility();
|
||||
@@ -618,7 +624,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
*/
|
||||
private static Card chooseCreature(final Player ai, CardCollection list) {
|
||||
// Creating a new combat for testing purposes.
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
final Player opponent = ai.getWeakestOpponent();
|
||||
Combat combat = new Combat(opponent);
|
||||
for (Card att : opponent.getCreaturesInPlay()) {
|
||||
combat.addAttacker(att, ai);
|
||||
@@ -926,7 +932,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
CardCollection blockers = currCombat.getBlockers(attacker);
|
||||
// Save my attacker by bouncing a blocker
|
||||
if (attacker.getController().equals(ai) && attacker.getShieldCount() == 0
|
||||
&& ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, currCombat)
|
||||
&& ComputerUtilCombat.attackerWouldBeDestroyed(ai, attacker, currCombat)
|
||||
&& !currCombat.getBlockers(attacker).isEmpty()) {
|
||||
ComputerUtilCard.sortByEvaluateCreature(blockers);
|
||||
Combat combat = new Combat(ai);
|
||||
@@ -985,20 +991,20 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
// bounce opponent's stuff
|
||||
list = CardLists.filterControlledBy(list, ai.getOpponents());
|
||||
if (!CardLists.getNotType(list, "Land").isEmpty()) {
|
||||
// When bouncing opponents stuff other than lands, don't bounce cards with CMC 0
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
for (Card aura : c.getEnchantedBy()) {
|
||||
// When bouncing opponents stuff other than lands, don't bounce cards with CMC 0
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
for (Card aura : c.getEnchantedBy()) {
|
||||
return aura.getController().isOpponentOf(ai);
|
||||
}
|
||||
if (blink) {
|
||||
return c.isToken();
|
||||
} else {
|
||||
return c.isToken() || c.getCMC() > 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (blink) {
|
||||
return c.isToken();
|
||||
} else {
|
||||
return c.isToken() || c.getCMC() > 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// TODO: Blink permanents with ETB triggers
|
||||
/*else if (!sa.isTrigger() && SpellAbilityAi.playReusable(ai, sa)) {
|
||||
@@ -1023,7 +1029,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
} else if (origin.contains(ZoneType.Graveyard)) {
|
||||
if (destination.equals(ZoneType.Exile) || destination.equals(ZoneType.Library)) {
|
||||
if (destination.equals(ZoneType.Exile) || destination.equals(ZoneType.Library)) {
|
||||
// Don't use these abilities before main 2 if possible
|
||||
if (!immediately && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& !sa.hasParam("ActivationPhases") && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
@@ -1035,7 +1041,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
&& !ComputerUtil.activateForCost(sa, ai)) {
|
||||
return false;
|
||||
}
|
||||
} else if (destination.equals(ZoneType.Hand)) {
|
||||
} else if (destination.equals(ZoneType.Hand)) {
|
||||
// only retrieve cards from computer graveyard
|
||||
list = CardLists.filterControlledBy(list, ai);
|
||||
} else if (sa.hasParam("AttachedTo")) {
|
||||
@@ -1065,8 +1071,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
if (destination.equals(ZoneType.Exile) || origin.contains(ZoneType.Battlefield)) {
|
||||
|
||||
// don't rush bouncing stuff when not going to attack
|
||||
if (!immediately && sa.getPayCosts() != null
|
||||
&& game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
if (!immediately && game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)
|
||||
&& ai.getCreaturesInPlay().isEmpty()) {
|
||||
return false;
|
||||
@@ -1097,14 +1102,14 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
// Only care about combatants during combat
|
||||
if (game.getPhaseHandler().inCombat() && origin.contains(ZoneType.Battlefield)) {
|
||||
CardCollection newList = CardLists.getValidCards(list, "Card.attacking,Card.blocking", null, null);
|
||||
if (!newList.isEmpty() || !sa.isTrigger()) {
|
||||
list = newList;
|
||||
}
|
||||
CardCollection newList = CardLists.getValidCards(list, "Card.attacking,Card.blocking", null, null);
|
||||
if (!newList.isEmpty() || !sa.isTrigger()) {
|
||||
list = newList;
|
||||
}
|
||||
}
|
||||
|
||||
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.getTargetRestrictions() != null
|
||||
&& sa.getTargetRestrictions().getMinTargets(source, sa) == 0 && sa.getPayCosts() != null
|
||||
boolean doWithoutTarget = sa.hasParam("Planeswalker") && sa.usesTargeting()
|
||||
&& sa.getTargetRestrictions().getMinTargets(source, sa) == 0
|
||||
&& sa.getPayCosts().hasSpecificCostType(CostPutCounter.class);
|
||||
|
||||
if (list.isEmpty() && !doWithoutTarget) {
|
||||
@@ -1325,11 +1330,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
Collections.sort(aiPlaneswalkers, new Comparator<Card>() {
|
||||
@Override
|
||||
public int compare(final Card a, final Card b) {
|
||||
return a.getCounters(CounterType.LOYALTY) - b.getCounters(CounterType.LOYALTY);
|
||||
return a.getCounters(CounterEnumType.LOYALTY) - b.getCounters(CounterEnumType.LOYALTY);
|
||||
}
|
||||
});
|
||||
for (Card pw : aiPlaneswalkers) {
|
||||
int curLoyalty = pw.getCounters(CounterType.LOYALTY);
|
||||
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||
int freshLoyalty = Integer.valueOf(pw.getCurrentState().getBaseLoyalty());
|
||||
if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
|
||||
return pw;
|
||||
@@ -1492,6 +1497,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
return SpecialCardAi.MairsilThePretender.considerCardFromList(fetchList);
|
||||
} else if ("SurvivalOfTheFittest".equals(logic)) {
|
||||
return SpecialCardAi.SurvivalOfTheFittest.considerCardToGet(decider, sa);
|
||||
} else if ("MazesEnd".equals(logic)) {
|
||||
return SpecialCardAi.MazesEnd.considerCardToGet(decider, sa);
|
||||
} else if ("Intuition".equals(logic)) {
|
||||
if (!multipleCardsToChoose.isEmpty()) {
|
||||
Card choice = multipleCardsToChoose.get(0);
|
||||
@@ -1618,7 +1625,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
// Called when looking for creature to attach aura or equipment
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
@@ -1627,7 +1634,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
// Currently only used by Curse of Misfortunes, so this branch should never get hit
|
||||
// But just in case it does, just select the first option
|
||||
return Iterables.getFirst(options, null);
|
||||
@@ -1790,6 +1797,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
public boolean doReturnCommanderLogic(SpellAbility sa, Player aiPlayer) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<AbilityKey, Object> originalParams = (Map<AbilityKey, Object>)sa.getReplacingObject(AbilityKey.OriginalParams);
|
||||
SpellAbility causeSa = (SpellAbility)originalParams.get(AbilityKey.Cause);
|
||||
SpellAbility causeSub = null;
|
||||
|
||||
@@ -11,6 +11,7 @@ import forge.util.MyRandom;
|
||||
import forge.util.collect.FCollection;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CharmAi extends SpellAbilityAi {
|
||||
@Override
|
||||
@@ -232,7 +233,7 @@ public class CharmAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents) {
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> opponents, Map<String, Object> params) {
|
||||
return Aggregates.random(opponents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
@@ -20,7 +21,7 @@ import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -99,7 +100,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
});
|
||||
return !choices.isEmpty();
|
||||
} else if (aiLogic.equals("Ashiok")) {
|
||||
final int loyalty = host.getCounters(CounterType.LOYALTY) - 1;
|
||||
final int loyalty = host.getCounters(CounterEnumType.LOYALTY) - 1;
|
||||
for (int i = loyalty; i >= 0; i--) {
|
||||
host.setSVar("ChosenX", "Number$" + i);
|
||||
choices = ai.getGame().getCardsIn(choiceZone);
|
||||
@@ -145,7 +146,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
final Card host = sa.getHostCard();
|
||||
final Player ctrl = host.getController();
|
||||
final String logic = sa.getParam("AILogic");
|
||||
@@ -233,7 +234,7 @@ public class ChooseCardAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
for (SpellAbility sa : c.getAllSpellAbilities()) {
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.getPayCosts().hasTapCost()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -23,7 +24,6 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
Card source = sa.getHostCard();
|
||||
if (sa.hasParam("AILogic")) {
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
@@ -54,13 +54,13 @@ public class ChooseCardNameAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) {
|
||||
// TODO - there is no AILogic implemented yet
|
||||
return false;
|
||||
return mandatory;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
|
||||
return ComputerUtilCard.getBestAI(options);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChooseCompanionAi extends SpellAbilityAi {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
List<Card> cards = Lists.newArrayList(options);
|
||||
if (cards.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Collections.shuffle(cards);
|
||||
return cards.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,6 @@ public class ChooseDirectionAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return canPlayAI(ai, sa);
|
||||
return mandatory || canPlayAI(ai, sa);
|
||||
}
|
||||
}
|
||||
|
||||
36
forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
Normal file
36
forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
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 {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
if (!sa.hasParam("AILogic")) {
|
||||
return false;
|
||||
}
|
||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
sa.resetTargets();
|
||||
Player opp = aiPlayer.getWeakestOpponent();
|
||||
if (sa.canTarget(opp)) {
|
||||
sa.getTargets().add(opp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
|
||||
return chance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
return mandatory || canPlayAI(ai, sa);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1);
|
||||
|
||||
// check for something which might prevent the counters to be placed on host
|
||||
if (!host.canReceiveCounters(CounterType.P1P1)) {
|
||||
if (!host.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||
return tokenSA;
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
// need a copy for one with extra +1/+1 counter boost,
|
||||
// without causing triggers to run
|
||||
final Card copy = CardUtil.getLKICopy(host);
|
||||
copy.setCounters(CounterType.P1P1, copy.getCounters(CounterType.P1P1) + n);
|
||||
copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n);
|
||||
copy.setZone(host.getZone());
|
||||
|
||||
// if host would put into the battlefield attacking
|
||||
@@ -281,10 +281,10 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
// TODO check for trigger to turn token ETB into +1/+1 counter for host
|
||||
// TODO check for trigger to turn token ETB into damage or life loss for opponent
|
||||
// in this cases Token might be prefered even if they would not survive
|
||||
final Card tokenCard = TokenAi.spawnToken(player, tokenSA, true);
|
||||
final Card tokenCard = TokenAi.spawnToken(player, tokenSA);
|
||||
|
||||
// Token would not survive
|
||||
if (tokenCard.getNetToughness() < 1) {
|
||||
if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) {
|
||||
return counterSA;
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
|
||||
game.getAction().checkStaticAbilities(false);
|
||||
|
||||
// can't gain counters, use Haste
|
||||
if (!copy.canReceiveCounters(CounterType.P1P1)) {
|
||||
if (!copy.canReceiveCounters(CounterEnumType.P1P1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ChoosePlayerAi extends SpellAbilityAi {
|
||||
@Override
|
||||
@@ -27,7 +28,7 @@ public class ChoosePlayerAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices) {
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> choices, Map<String, Object> params) {
|
||||
Player chosen = null;
|
||||
if ("Curse".equals(sa.getParam("AILogic"))) {
|
||||
for (Player pc : choices) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
@@ -126,7 +127,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
public Card chooseSingleCard(final Player aiChoser, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
if ("NeedsPrevention".equals(sa.getParam("AILogic"))) {
|
||||
final Player ai = sa.getActivatingPlayer();
|
||||
final Game game = ai.getGame();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.ComputerUtilCard;
|
||||
|
||||
@@ -56,7 +58,7 @@ public class ClashAi extends SpellAbilityAi {
|
||||
* forge.game.spellability.SpellAbility, java.lang.Iterable)
|
||||
*/
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
for (Player p : options) {
|
||||
if (p.getCardsIn(ZoneType.Library).size() == 0)
|
||||
return p;
|
||||
@@ -82,7 +84,7 @@ public class ClashAi extends SpellAbilityAi {
|
||||
|
||||
PlayerCollection players = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
// use chooseSinglePlayer function to the select player
|
||||
Player chosen = chooseSinglePlayer(ai, sa, players);
|
||||
Player chosen = chooseSinglePlayer(ai, sa, players, null);
|
||||
if (chosen != null) {
|
||||
sa.resetTargets();
|
||||
sa.getTargets().add(chosen);
|
||||
|
||||
@@ -15,6 +15,7 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CloneAi extends SpellAbilityAi {
|
||||
|
||||
@@ -169,7 +170,7 @@ public class CloneAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
Player targetedPlayer) {
|
||||
Player targetedPlayer, Map<String, Object> params) {
|
||||
|
||||
final Card host = sa.getHostCard();
|
||||
final Player ctrl = host.getController();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -302,7 +303,7 @@ public class ControlGainAi extends SpellAbilityAi {
|
||||
} // pumpDrawbackAI()
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
final List<Card> cards = Lists.newArrayList();
|
||||
for (Player p : options) {
|
||||
cards.addAll(p.getCreaturesInPlay());
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.ai.ComputerUtilCard;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* AbilityFactory_GainControlVariant class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: AbilityFactoryGainControl.java 17764 2012-10-29 11:04:18Z Sloth $
|
||||
*/
|
||||
public class ControlGainVariantAi extends SpellAbilityAi {
|
||||
@Override
|
||||
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
|
||||
|
||||
String logic = sa.getParam("AILogic");
|
||||
|
||||
if ("GainControlOwns".equals(logic)) {
|
||||
List<Card> list = CardLists.filter(ai.getGame().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
return crd.isCreature() && !crd.getController().equals(crd.getOwner());
|
||||
}
|
||||
});
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (final Card c : list) {
|
||||
if (ai.equals(c.getController())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
Iterable<Card> otherCtrl = CardLists.filter(options, Predicates.not(CardPredicates.isController(ai)));
|
||||
if (Iterables.isEmpty(otherCtrl)) {
|
||||
return ComputerUtilCard.getWorstAI(options);
|
||||
} else {
|
||||
return ComputerUtilCard.getBestAI(otherCtrl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CopyPermanentAi extends SpellAbilityAi {
|
||||
@Override
|
||||
@@ -204,7 +205,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
// Select a card to attach to
|
||||
CardCollection betterOptions = getBetterOptions(ai, sa, options, isOptional);
|
||||
if (!betterOptions.isEmpty()) {
|
||||
@@ -223,7 +224,7 @@ public class CopyPermanentAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
final List<Card> cards = new PlayerCollection(options).getCreaturesInPlay();
|
||||
Card chosen = ComputerUtilCard.getBestCreatureAI(cards);
|
||||
return chosen != null ? chosen.getController() : Iterables.getFirst(options, null);
|
||||
|
||||
@@ -29,8 +29,8 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
|
||||
|
||||
final SpellAbility top = game.getStack().peekAbility();
|
||||
if (top != null
|
||||
&& top.getPayCosts() != null && top.getPayCosts().getCostMana() != null
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null
|
||||
&& top.getPayCosts().getCostMana() != null
|
||||
&& sa.getPayCosts().getCostMana() != null
|
||||
&& top.getPayCosts().getCostMana().getMana().getCMC() >= sa.getPayCosts().getCostMana().getMana().getCMC() + diff) {
|
||||
// The copied spell has a significantly higher CMC than the copy spell, consider copying
|
||||
chance = 100;
|
||||
|
||||
@@ -26,7 +26,7 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
@@ -97,7 +97,7 @@ public abstract class CountersAi {
|
||||
final CardCollection boon = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
return c.getCounters(CounterType.DIVINITY) == 0;
|
||||
return c.getCounters(CounterEnumType.DIVINITY) == 0;
|
||||
}
|
||||
});
|
||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(boon, null, false);
|
||||
|
||||
@@ -42,14 +42,14 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final Card host = sa.getHostCard();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.getType(type);
|
||||
|
||||
// Don't tap creatures that may be able to block
|
||||
if (ComputerUtil.waitForBlocking(sa)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CounterType.P1P1.equals(cType) && sa.hasParam("Source")) {
|
||||
if (CounterEnumType.P1P1.equals(cType) && sa.hasParam("Source")) {
|
||||
int amount = calcAmount(sa, cType);
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||
@@ -92,7 +92,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
// for Simic Fluxmage and other
|
||||
return ph.getNextTurn().equals(ai) && !ph.getPhase().isBefore(PhaseType.END_OF_TURN);
|
||||
|
||||
} else if (CounterType.P1P1.equals(cType) && sa.hasParam("Defined")) {
|
||||
} else if (CounterEnumType.P1P1.equals(cType) && sa.hasParam("Defined")) {
|
||||
// something like Cyptoplast Root-kin
|
||||
if (ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||
if (ph.inCombat() && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
@@ -115,6 +115,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(final Player ai, SpellAbility sa, boolean mandatory) {
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
|
||||
if (!moveTgtAI(ai, sa) && !mandatory) {
|
||||
return false;
|
||||
@@ -142,7 +143,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.getType(type);
|
||||
|
||||
final List<Card> srcCards = AbilityUtils.getDefinedCards(host, sa.getParam("Source"), sa);
|
||||
final List<Card> destCards = AbilityUtils.getDefinedCards(host, sa.getParam("Defined"), sa);
|
||||
@@ -189,7 +190,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
|
||||
// check for some specific AI preferences
|
||||
if ("DontMoveCounterIfLethal".equals(sa.getParam("AILogic"))) {
|
||||
return cType != CounterType.P1P1 || src.getNetToughness() - src.getTempToughnessBoost() - 1 > 0;
|
||||
return !cType.is(CounterEnumType.P1P1) || src.getNetToughness() - src.getTempToughnessBoost() - 1 > 0;
|
||||
}
|
||||
}
|
||||
// no target
|
||||
@@ -234,7 +235,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
final Card host = sa.getHostCard();
|
||||
final Game game = ai.getGame();
|
||||
final String type = sa.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(type) ? null : CounterType.valueOf(type);
|
||||
final CounterType cType = "Any".equals(type) || "All".equals(type) ? null : CounterType.getType(type);
|
||||
|
||||
List<Card> tgtCards = CardLists.getTargetableCards(game.getCardsIn(ZoneType.Battlefield), sa);
|
||||
|
||||
@@ -278,7 +279,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
|
||||
// do not steal a P1P1 from Undying if it would die
|
||||
// this way
|
||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||
if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING) || card.isToken();
|
||||
}
|
||||
return true;
|
||||
@@ -321,13 +322,13 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// try to remove P1P1 from undying or evolve
|
||||
if (CounterType.P1P1.equals(cType)) {
|
||||
if (CounterEnumType.P1P1.equals(cType)) {
|
||||
if (card.hasKeyword(Keyword.UNDYING) || card.hasKeyword(Keyword.EVOLVE)
|
||||
|| card.hasKeyword(Keyword.ADAPT)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
||||
if (CounterEnumType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -382,10 +383,10 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (cType != null) {
|
||||
if (CounterType.P1P1.equals(cType) && card.hasKeyword(Keyword.UNDYING)) {
|
||||
if (CounterEnumType.P1P1.equals(cType) && card.hasKeyword(Keyword.UNDYING)) {
|
||||
return false;
|
||||
}
|
||||
if (CounterType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
||||
if (CounterEnumType.M1M1.equals(cType) && card.hasKeyword(Keyword.PERSIST)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -393,7 +394,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -452,7 +453,7 @@ public class CountersMoveAi extends SpellAbilityAi {
|
||||
// or for source -> multiple defined
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
Player targetedPlayer) {
|
||||
Player targetedPlayer, Map<String, Object> params) {
|
||||
if (sa.hasParam("AiLogic")) {
|
||||
String logic = sa.getParam("AiLogic");
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -77,7 +78,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
final CounterType counterType = getCounterType(sa);
|
||||
|
||||
if (!CounterType.P1P1.equals(counterType) && counterType != null) {
|
||||
if (!CounterEnumType.P1P1.equals(counterType) && counterType != null) {
|
||||
if (!sa.hasParam("ActivationPhases")) {
|
||||
// Don't use non P1P1/M1M1 counters before main 2 if possible
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && !ComputerUtil.castSpellInMain1(ai, sa)) {
|
||||
@@ -147,15 +148,15 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
if (!aiList.isEmpty()) {
|
||||
// counter type list to check
|
||||
// first loyalty, then P1P!, then Charge Counter
|
||||
List<CounterType> typeList = Lists.newArrayList(CounterType.LOYALTY, CounterType.P1P1, CounterType.CHARGE);
|
||||
for (CounterType type : typeList) {
|
||||
List<CounterEnumType> typeList = Lists.newArrayList(CounterEnumType.LOYALTY, CounterEnumType.P1P1, CounterEnumType.CHARGE);
|
||||
for (CounterEnumType type : typeList) {
|
||||
// enough targets
|
||||
if (!sa.canAddMoreTarget()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (counterType == null || counterType == type) {
|
||||
addTargetsByCounterType(ai, sa, aiList, type);
|
||||
if (counterType == null || counterType.is(type)) {
|
||||
addTargetsByCounterType(ai, sa, aiList, CounterType.get(type));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,7 +165,7 @@ public class CountersMultiplyAi extends SpellAbilityAi {
|
||||
if (!oppList.isEmpty()) {
|
||||
// not enough targets
|
||||
if (sa.canAddMoreTarget()) {
|
||||
final CounterType type = CounterType.M1M1;
|
||||
final CounterType type = CounterType.get(CounterEnumType.M1M1);
|
||||
if (counterType == null || counterType == type) {
|
||||
addTargetsByCounterType(ai, sa, oppList, type);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -32,7 +33,7 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
|
||||
for (final Player p : allies) {
|
||||
// player has experience or energy counter
|
||||
if (p.getCounters(CounterType.EXPERIENCE) + p.getCounters(CounterType.ENERGY) >= 1) {
|
||||
if (p.getCounters(CounterEnumType.EXPERIENCE) + p.getCounters(CounterEnumType.ENERGY) >= 1) {
|
||||
allyExpOrEnergy = true;
|
||||
}
|
||||
cperms.addAll(CardLists.filter(p.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@@ -115,17 +116,19 @@ public class CountersProliferateAi extends SpellAbilityAi {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer) {
|
||||
public <T extends GameEntity> T chooseSingleEntity(Player ai, SpellAbility sa, Collection<T> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
// Proliferate is always optional for all, no need to select best
|
||||
|
||||
final CounterType poison = CounterType.get(CounterEnumType.POISON);
|
||||
|
||||
// because countertype can't be chosen anymore, only look for posion counters
|
||||
for (final Player p : Iterables.filter(options, Player.class)) {
|
||||
if (p.isOpponentOf(ai)) {
|
||||
if (p.getCounters(CounterType.POISON) > 0 && p.canReceiveCounters(CounterType.POISON)) {
|
||||
if (p.getCounters(poison) > 0 && p.canReceiveCounters(poison)) {
|
||||
return (T)p;
|
||||
}
|
||||
} else {
|
||||
if (p.getCounters(CounterType.POISON) <= 5 || p.canReceiveCounters(CounterType.POISON)) {
|
||||
if (p.getCounters(poison) <= 5 || p.canReceiveCounters(poison)) {
|
||||
return (T)p;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,17 +56,17 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
final CostRemoveCounter remCounter = (CostRemoveCounter) part;
|
||||
final CounterType counterType = remCounter.counter;
|
||||
if (counterType.name().equals(type) && !aiLogic.startsWith("MoveCounter")) {
|
||||
if (counterType.getName().equals(type) && !aiLogic.startsWith("MoveCounter")) {
|
||||
return false;
|
||||
}
|
||||
if (!part.payCostFromSource()) {
|
||||
if (counterType.equals(CounterType.P1P1)) {
|
||||
if (counterType.is(CounterEnumType.P1P1)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// don't kill the creature
|
||||
if (counterType.equals(CounterType.P1P1) && source.getLethalDamage() <= 1) {
|
||||
if (counterType.is(CounterEnumType.P1P1) && source.getLethalDamage() <= 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
int maxLevel = Integer.parseInt(sa.getParam("MaxLevel"));
|
||||
return source.getCounters(CounterType.LEVEL) < maxLevel;
|
||||
return source.getCounters(CounterEnumType.LEVEL) < maxLevel;
|
||||
}
|
||||
|
||||
return super.checkPhaseRestrictions(ai, sa, ph);
|
||||
@@ -146,7 +146,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (abTgt.canTgtPlayer()) {
|
||||
// try to kill opponent with Poison
|
||||
PlayerCollection oppList = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||
PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterType.POISON, 9));
|
||||
PlayerCollection poisonList = oppList.filter(PlayerPredicates.hasCounter(CounterEnumType.POISON, 9));
|
||||
if (!poisonList.isEmpty()) {
|
||||
sa.getTargets().add(poisonList.max(PlayerPredicates.compareByLife()));
|
||||
return true;
|
||||
@@ -157,13 +157,13 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
// try to kill creature with -1/-1 counters if it can
|
||||
// receive counters, execpt it has undying
|
||||
CardCollection oppCreat = CardLists.getTargetableCards(ai.getOpponents().getCreaturesInPlay(), sa);
|
||||
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
CardCollection oppCreatM1 = CardLists.filter(oppCreat, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||
oppCreatM1 = CardLists.getNotKeyword(oppCreatM1, Keyword.UNDYING);
|
||||
|
||||
oppCreatM1 = CardLists.filter(oppCreatM1, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
return input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.M1M1);
|
||||
return input.getNetToughness() <= 1 && input.canReceiveCounters(CounterType.get(CounterEnumType.M1M1));
|
||||
}
|
||||
|
||||
});
|
||||
@@ -220,8 +220,10 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
|
||||
if ("Never".equals(logic)) {
|
||||
return false;
|
||||
} else if ("AlwaysWithNoTgt".equals(logic)) {
|
||||
return true;
|
||||
} else if ("AristocratCounters".equals(logic)) {
|
||||
return PumpAi.doAristocratWithCountersLogic(sa, ai);
|
||||
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
|
||||
} else if ("PayEnergy".equals(logic)) {
|
||||
return true;
|
||||
} else if ("PayEnergyConservatively".equals(logic)) {
|
||||
@@ -242,7 +244,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
int totBlkPower = Aggregates.sum(blocked, CardPredicates.Accessors.fnGetNetPower);
|
||||
int totBlkToughness = Aggregates.min(blocked, CardPredicates.Accessors.fnGetNetToughness);
|
||||
|
||||
int numActivations = ai.getCounters(CounterType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
|
||||
int numActivations = ai.getCounters(CounterEnumType.ENERGY) / sa.getPayCosts().getCostEnergy().convertAmount();
|
||||
if (sa.getHostCard().getNetToughness() + numActivations > totBlkPower
|
||||
|| sa.getHostCard().getNetPower() + numActivations >= totBlkToughness) {
|
||||
return true;
|
||||
@@ -257,7 +259,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.ACTIVATED_THIS_TURN);
|
||||
return true;
|
||||
}
|
||||
} else if (ai.getCounters(CounterType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
|
||||
} else if (ai.getCounters(CounterEnumType.ENERGY) > ComputerUtilCard.getMaxSAEnergyCostOnBattlefield(ai) + sa.getPayCosts().getCostEnergy().convertAmount()) {
|
||||
// outside of combat, this logic only works if the relevant AI profile option is enabled
|
||||
// and if there is enough energy saved
|
||||
if (!onlyInCombat) {
|
||||
@@ -320,7 +322,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
Game game = ai.getGame();
|
||||
Combat combat = game.getCombat();
|
||||
|
||||
if (!source.canReceiveCounters(CounterType.P1P1) || source.getCounters(CounterType.P1P1) > 0) {
|
||||
if (!source.canReceiveCounters(CounterType.get(CounterEnumType.P1P1)) || source.getCounters(CounterEnumType.P1P1) > 0) {
|
||||
return false;
|
||||
} else if (combat != null && ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||
return doCombatAdaptLogic(source, amount, combat);
|
||||
@@ -343,7 +345,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (isClockwork) {
|
||||
// Clockwork Avian and other similar cards: do not tap all mana for X,
|
||||
// instead only rewind to max allowed value when the power gets low enough.
|
||||
int curCtrs = source.getCounters(CounterType.P1P0);
|
||||
int curCtrs = source.getCounters(CounterEnumType.P1P0);
|
||||
int maxCtrs = Integer.parseInt(sa.getParam("MaxFromEffect"));
|
||||
|
||||
// This will "rewind" clockwork cards when they fall to 50% power or below, consider improving
|
||||
@@ -433,7 +435,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (sacSelf && c.equals(source)) {
|
||||
return false;
|
||||
}
|
||||
return sa.canTarget(c) && c.canReceiveCounters(CounterType.valueOf(type));
|
||||
return sa.canTarget(c) && c.canReceiveCounters(CounterType.getType(type));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -452,7 +454,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
// but try to do it in Main 2 then so that the AI has a chance to play creatures first.
|
||||
if (list.isEmpty()
|
||||
&& sa.hasParam("Planeswalker")
|
||||
&& sa.getPayCosts() != null
|
||||
&& sa.getPayCosts().hasOnlySpecificCostType(CostPutCounter.class)
|
||||
&& sa.isTargetNumberValid()
|
||||
&& sa.getTargets().getNumTargeted() == 0
|
||||
@@ -557,7 +558,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int currCounters = cards.get(0).getCounters(CounterType.valueOf(type));
|
||||
final int currCounters = cards.get(0).getCounters(CounterType.get(type));
|
||||
// each non +1/+1 counter on the card is a 10% chance of not
|
||||
// activating this ability.
|
||||
|
||||
@@ -704,7 +705,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
SpellAbility testSa = sa;
|
||||
int countX = 0;
|
||||
int nonXGlyphs = 0;
|
||||
while (testSa != null && testSa.getPayCosts() != null && countX == 0) {
|
||||
while (testSa != null && countX == 0) {
|
||||
countX = testSa.getPayCosts().getTotalMana().countX();
|
||||
nonXGlyphs = testSa.getPayCosts().getTotalMana().getGlyphCount() - countX;
|
||||
testSa = testSa.getSubAbility();
|
||||
@@ -732,6 +733,21 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
// put a counter?
|
||||
// things like Powder Keg, which are way too complex for the AI
|
||||
}
|
||||
} else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) {
|
||||
// can only target opponent
|
||||
List<Player> playerList = Lists.newArrayList(Iterables.filter(
|
||||
sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class));
|
||||
|
||||
if (playerList.isEmpty() && mandatory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// try to choose player with less creatures
|
||||
Player choice = Collections.min(playerList, PlayerPredicates.compareByZoneSize(ZoneType.Battlefield, CardPredicates.Presets.CREATURES));
|
||||
|
||||
if (choice != null) {
|
||||
sa.getTargets().add(choice);
|
||||
}
|
||||
} else {
|
||||
if (sa.isCurse()) {
|
||||
list = ai.getOpponents().getCardsIn(ZoneType.Battlefield);
|
||||
@@ -873,7 +889,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
// used by Tribute, select player with lowest Life
|
||||
// TODO add more logic using TributeAILogic
|
||||
List<Player> list = Lists.newArrayList(options);
|
||||
@@ -881,16 +897,20 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
protected Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
// Bolster does use this
|
||||
// TODO need more or less logic there?
|
||||
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
||||
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
||||
|
||||
// no logic if there is no options or no to choice
|
||||
if (!isOptional && Iterables.size(options) <= 1) {
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
||||
final CounterType type = params.containsKey("CounterType") ? (CounterType)params.get("CounterType")
|
||||
: CounterType.getType(sa.getParam("CounterType"));
|
||||
|
||||
final String amountStr = sa.getParamOrDefault("CounterNum", "1");
|
||||
final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa);
|
||||
|
||||
@@ -907,7 +927,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
return false;
|
||||
if (ComputerUtilCard.isUselessCreature(ai, input))
|
||||
return false;
|
||||
if (CounterType.M1M1.equals(type) && amount >= input.getNetToughness())
|
||||
if (type.is(CounterEnumType.M1M1) && amount >= input.getNetToughness())
|
||||
return true;
|
||||
return ComputerUtil.isNegativeCounter(type, input);
|
||||
}
|
||||
@@ -931,6 +951,20 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
|
||||
CardCollection filtered = mine;
|
||||
|
||||
// Try to filter out keywords that we already have on cards
|
||||
if (type.isKeywordCounter()) {
|
||||
Keyword kw = Keyword.smartValueOf(type.getName());
|
||||
final CardCollection doNotHaveKeyword = CardLists.filter(filtered, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return !card.hasKeyword(kw) && card.canBeTargetedBy(sa) && sa.canTarget(card);
|
||||
}
|
||||
});
|
||||
|
||||
if (doNotHaveKeyword.size() > 0)
|
||||
filtered = doNotHaveKeyword;
|
||||
}
|
||||
|
||||
final CardCollection notUseless = CardLists.filter(filtered, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
@@ -945,26 +979,26 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// some special logic to reload Persist/Undying
|
||||
if (CounterType.P1P1.equals(type)) {
|
||||
if (p1p1.equals(type)) {
|
||||
final CardCollection persist = CardLists.filter(filtered, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
if (!input.hasKeyword(Keyword.PERSIST))
|
||||
return false;
|
||||
return input.getCounters(CounterType.M1M1) <= amount;
|
||||
return input.getCounters(m1m1) <= amount;
|
||||
}
|
||||
});
|
||||
|
||||
if (!persist.isEmpty()) {
|
||||
filtered = persist;
|
||||
}
|
||||
} else if (CounterType.M1M1.equals(type)) {
|
||||
} else if (m1m1.equals(type)) {
|
||||
final CardCollection undying = CardLists.filter(filtered, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card input) {
|
||||
if (!input.hasKeyword(Keyword.UNDYING))
|
||||
return false;
|
||||
return input.getCounters(CounterType.P1P1) <= amount && input.getNetToughness() > amount;
|
||||
return input.getCounters(p1p1) <= amount && input.getNetToughness() > amount;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -987,8 +1021,8 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
if (e instanceof Card) {
|
||||
Card c = (Card) e;
|
||||
if (c.getController().isOpponentOf(ai)) {
|
||||
if (options.contains(CounterType.M1M1) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.M1M1;
|
||||
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && !c.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (ComputerUtil.isNegativeCounter(type, c)) {
|
||||
@@ -1005,12 +1039,12 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
} else if (e instanceof Player) {
|
||||
Player p = (Player) e;
|
||||
if (p.isOpponentOf(ai)) {
|
||||
if (options.contains(CounterType.POISON)) {
|
||||
return CounterType.POISON;
|
||||
if (options.contains(CounterType.get(CounterEnumType.POISON))) {
|
||||
return CounterType.get(CounterEnumType.POISON);
|
||||
}
|
||||
} else {
|
||||
if (options.contains(CounterType.EXPERIENCE)) {
|
||||
return CounterType.EXPERIENCE;
|
||||
if (options.contains(CounterType.get(CounterEnumType.EXPERIENCE))) {
|
||||
return CounterType.get(CounterEnumType.EXPERIENCE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
|
||||
//Check for cards that could profit from the ability
|
||||
PhaseHandler phase = ai.getGame().getPhaseHandler();
|
||||
if (type.equals("P1P1") && sa.isAbility() && source.isCreature()
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
||||
&& sa.getPayCosts().hasTapCost()
|
||||
&& sa instanceof AbilitySub
|
||||
&& (!phase.getNextTurn().equals(ai)
|
||||
|| phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS))) {
|
||||
|
||||
@@ -75,7 +75,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
|
||||
if (sa.hasParam("CounterType")) {
|
||||
// currently only Jhoira's Timebug
|
||||
final CounterType type = CounterType.valueOf(sa.getParam("CounterType"));
|
||||
final CounterType type = CounterType.getType(sa.getParam("CounterType"));
|
||||
|
||||
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounter(type, amount));
|
||||
|
||||
@@ -100,7 +100,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = CardLists.filter(countersList,
|
||||
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterType.ICE));
|
||||
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterEnumType.ICE));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
sa.getTargets().add(depthsList.getFirst());
|
||||
@@ -113,7 +113,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
CardCollection planeswalkerList = CardLists.filter(
|
||||
CardLists.filterControlledBy(countersList, ai.getOpponents()),
|
||||
CardPredicates.Presets.PLANESWALKERS,
|
||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||
CardPredicates.hasLessCounter(CounterEnumType.LOYALTY, amount));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||
@@ -123,7 +123,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
// do as M1M1 part
|
||||
CardCollection aiList = CardLists.filterControlledBy(countersList, ai);
|
||||
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||
|
||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
|
||||
if (!aiPersistList.isEmpty()) {
|
||||
@@ -136,7 +136,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// do as P1P1 part
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.P1P1));
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.P1P1));
|
||||
CardCollection aiUndyingList = CardLists.getKeyword(aiM1M1List, Keyword.UNDYING);
|
||||
|
||||
if (!aiUndyingList.isEmpty()) {
|
||||
@@ -199,18 +199,18 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
Card tgt = (Card) params.get("Target");
|
||||
|
||||
// planeswalker has high priority for loyalty counters
|
||||
if (tgt.isPlaneswalker() && options.contains(CounterType.LOYALTY)) {
|
||||
return CounterType.LOYALTY;
|
||||
if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) {
|
||||
return CounterType.get(CounterEnumType.LOYALTY);
|
||||
}
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
// creatures with BaseToughness below or equal zero might be
|
||||
// killed if their counters are removed
|
||||
if (tgt.isCreature() && tgt.getBaseToughness() <= 0) {
|
||||
if (options.contains(CounterType.P1P1)) {
|
||||
return CounterType.P1P1;
|
||||
} else if (options.contains(CounterType.M1M1)) {
|
||||
return CounterType.M1M1;
|
||||
if (options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
} else if (options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,14 +222,14 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
} else {
|
||||
// this counters are treat first to be removed
|
||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.ICE)) {
|
||||
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
return CounterType.ICE;
|
||||
return CounterType.get(CounterEnumType.ICE);
|
||||
}
|
||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.P1P1)) {
|
||||
return CounterType.P1P1;
|
||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.M1M1)) {
|
||||
return CounterType.M1M1;
|
||||
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
} else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
}
|
||||
|
||||
// fallback logic, select positive counter to add more
|
||||
@@ -262,19 +262,19 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
|
||||
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
|
||||
|
||||
if (tgt.getController().isOpponentOf(ai)) {
|
||||
if (type.equals(CounterType.LOYALTY) && tgt.isPlaneswalker()) {
|
||||
if (type.is(CounterEnumType.LOYALTY) && tgt.isPlaneswalker()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ComputerUtil.isNegativeCounter(type, tgt);
|
||||
} else {
|
||||
if (type.equals(CounterType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
||||
if (type.is(CounterEnumType.ICE) && "Dark Depths".equals(tgt.getName())) {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
return false;
|
||||
}
|
||||
} else if (type.equals(CounterType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
|
||||
} else if (type.is(CounterEnumType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
|
||||
return false;
|
||||
} else if (type.equals(CounterType.P1P1) && tgt.hasKeyword(Keyword.UNDYING)) {
|
||||
} else if (type.is(CounterEnumType.P1P1) && tgt.hasKeyword(Keyword.UNDYING)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (!type.matches("Any") && !type.matches("All")) {
|
||||
final int currCounters = sa.getHostCard().getCounters(CounterType.valueOf(type));
|
||||
final int currCounters = sa.getHostCard().getCounters(CounterType.getType(type));
|
||||
if (currCounters < 1) {
|
||||
return false;
|
||||
}
|
||||
@@ -119,7 +119,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterType.ICE, 3));
|
||||
CardPredicates.hasCounter(CounterEnumType.ICE, 3));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
sa.getTargets().add(depthsList.getFirst());
|
||||
@@ -132,7 +132,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
list = CardLists.filter(list, CardPredicates.isTargetableBy(sa));
|
||||
|
||||
CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS,
|
||||
CardPredicates.hasCounter(CounterType.LOYALTY, 5));
|
||||
CardPredicates.hasCounter(CounterEnumType.LOYALTY, 5));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList));
|
||||
@@ -159,11 +159,11 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
|
||||
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
|
||||
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
|
||||
CardPredicates.hasCounter(CounterType.ICE));
|
||||
CardPredicates.hasCounter(CounterEnumType.ICE));
|
||||
|
||||
if (!depthsList.isEmpty()) {
|
||||
Card depth = depthsList.getFirst();
|
||||
int ice = depth.getCounters(CounterType.ICE);
|
||||
int ice = depth.getCounters(CounterEnumType.ICE);
|
||||
if (amount >= ice) {
|
||||
sa.getTargets().add(depth);
|
||||
if (xPay) {
|
||||
@@ -180,7 +180,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
|
||||
CardCollection planeswalkerList = CardLists.filter(list,
|
||||
Predicates.and(CardPredicates.Presets.PLANESWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
||||
CardPredicates.hasLessCounter(CounterType.LOYALTY, amount));
|
||||
CardPredicates.hasLessCounter(CounterEnumType.LOYALTY, amount));
|
||||
|
||||
if (!planeswalkerList.isEmpty()) {
|
||||
Card best = ComputerUtilCard.getBestPlaneswalkerAI(planeswalkerList);
|
||||
@@ -196,7 +196,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// do as M1M1 part
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1));
|
||||
CardCollection aiM1M1List = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1));
|
||||
|
||||
CardCollection aiPersistList = CardLists.getKeyword(aiM1M1List, Keyword.PERSIST);
|
||||
if (!aiPersistList.isEmpty()) {
|
||||
@@ -209,7 +209,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// do as P1P1 part
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasLessCounter(CounterType.P1P1, amount));
|
||||
CardCollection aiP1P1List = CardLists.filter(aiList, CardPredicates.hasLessCounter(CounterEnumType.P1P1, amount));
|
||||
CardCollection aiUndyingList = CardLists.getKeyword(aiP1P1List, Keyword.UNDYING);
|
||||
|
||||
if (!aiUndyingList.isEmpty()) {
|
||||
@@ -220,7 +220,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// remove P1P1 counters from opposing creatures
|
||||
CardCollection oppP1P1List = CardLists.filter(list,
|
||||
Predicates.and(CardPredicates.Presets.CREATURES, CardPredicates.isControlledByAnyOf(ai.getOpponents())),
|
||||
CardPredicates.hasCounter(CounterType.P1P1));
|
||||
CardPredicates.hasCounter(CounterEnumType.P1P1));
|
||||
if (!oppP1P1List.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getBestCreatureAI(oppP1P1List));
|
||||
return true;
|
||||
@@ -244,7 +244,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// no special amount for that one yet
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
CardCollection aiList = CardLists.filterControlledBy(list, ai);
|
||||
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterType.M1M1, amount));
|
||||
aiList = CardLists.filter(aiList, CardPredicates.hasCounter(CounterEnumType.M1M1, amount));
|
||||
|
||||
CardCollection aiPersist = CardLists.getKeyword(aiList, Keyword.PERSIST);
|
||||
if (!aiPersist.isEmpty()) {
|
||||
@@ -263,7 +263,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
// no special amount for that one yet
|
||||
int amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
|
||||
list = CardLists.filter(list, CardPredicates.hasCounter(CounterType.P1P1, amount));
|
||||
list = CardLists.filter(list, CardPredicates.hasCounter(CounterEnumType.P1P1, amount));
|
||||
|
||||
// currently only logic for Bloodcrazed Hoplite, but add logic for
|
||||
// targeting ai creatures too
|
||||
@@ -309,12 +309,12 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
amount = AbilityUtils.calculateAmount(source, amountStr, sa);
|
||||
}
|
||||
|
||||
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterType.TIME, amount));
|
||||
CardCollection timeList = CardLists.filter(list, CardPredicates.hasLessCounter(CounterEnumType.TIME, amount));
|
||||
|
||||
if (!timeList.isEmpty()) {
|
||||
Card best = ComputerUtilCard.getBestAI(timeList);
|
||||
|
||||
int timeCount = best.getCounters(CounterType.TIME);
|
||||
int timeCount = best.getCounters(CounterEnumType.TIME);
|
||||
sa.getTargets().add(best);
|
||||
if (xPay) {
|
||||
source.setSVar("PayX", Integer.toString(timeCount));
|
||||
@@ -335,7 +335,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
CardCollection outlastCreats = CardLists.filter(list, CardPredicates.hasKeyword(Keyword.OUTLAST));
|
||||
if (!outlastCreats.isEmpty()) {
|
||||
// outlast cards often benefit from having +1/+1 counters, try not to remove last one
|
||||
CardCollection betterTargets = CardLists.filter(outlastCreats, CardPredicates.hasCounter(CounterType.P1P1, 2));
|
||||
CardCollection betterTargets = CardLists.filter(outlastCreats, CardPredicates.hasCounter(CounterEnumType.P1P1, 2));
|
||||
|
||||
if (!betterTargets.isEmpty()) {
|
||||
sa.getTargets().add(ComputerUtilCard.getWorstAI(betterTargets));
|
||||
@@ -377,8 +377,8 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
if (targetCard.getController().isOpponentOf(player)) {
|
||||
return !ComputerUtil.isNegativeCounter(type, targetCard) ? max : min;
|
||||
} else {
|
||||
if (targetCard.hasKeyword(Keyword.UNDYING) && type == CounterType.P1P1
|
||||
&& targetCard.getCounters(CounterType.P1P1) >= max) {
|
||||
if (targetCard.hasKeyword(Keyword.UNDYING) && type.is(CounterEnumType.P1P1)
|
||||
&& targetCard.getCounters(CounterEnumType.P1P1) >= max) {
|
||||
return max;
|
||||
}
|
||||
|
||||
@@ -387,9 +387,9 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
} else if (target instanceof Player) {
|
||||
Player targetPlayer = (Player) target;
|
||||
if (targetPlayer.isOpponentOf(player)) {
|
||||
return !type.equals(CounterType.POISON) ? max : min;
|
||||
return !type.equals(CounterEnumType.POISON) ? max : min;
|
||||
} else {
|
||||
return type.equals(CounterType.POISON) ? max : min;
|
||||
return type.equals(CounterEnumType.POISON) ? max : min;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
if (targetCard.getController().isOpponentOf(ai)) {
|
||||
// if its a Planeswalker try to remove Loyality first
|
||||
if (targetCard.isPlaneswalker()) {
|
||||
return CounterType.LOYALTY;
|
||||
return CounterType.get(CounterEnumType.LOYALTY);
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (!ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||
@@ -423,10 +423,10 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (options.contains(CounterType.M1M1) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
||||
return CounterType.M1M1;
|
||||
} else if (options.contains(CounterType.P1P1) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.P1P1;
|
||||
if (options.contains(CounterType.get(CounterEnumType.M1M1)) && targetCard.hasKeyword(Keyword.PERSIST)) {
|
||||
return CounterType.get(CounterEnumType.M1M1);
|
||||
} else if (options.contains(CounterType.get(CounterEnumType.P1P1)) && targetCard.hasKeyword(Keyword.UNDYING)) {
|
||||
return CounterType.get(CounterEnumType.P1P1);
|
||||
}
|
||||
for (CounterType type : options) {
|
||||
if (ComputerUtil.isNegativeCounter(type, targetCard)) {
|
||||
@@ -438,13 +438,13 @@ public class CountersRemoveAi extends SpellAbilityAi {
|
||||
Player targetPlayer = (Player) target;
|
||||
if (targetPlayer.isOpponentOf(ai)) {
|
||||
for (CounterType type : options) {
|
||||
if (!type.equals(CounterType.POISON)) {
|
||||
if (!type.equals(CounterEnumType.POISON)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (CounterType type : options) {
|
||||
if (type.equals(CounterType.POISON)) {
|
||||
if (type.equals(CounterEnumType.POISON)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.keyword.Keyword;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -50,10 +50,9 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
}
|
||||
if (damage.equals("ChosenX")) {
|
||||
x = source.getCounters(CounterType.LOYALTY);
|
||||
x = source.getCounters(CounterEnumType.LOYALTY);
|
||||
}
|
||||
if (x == -1) {
|
||||
Player bestOpp = determineOppToKill(ai, sa, source, dmg);
|
||||
if (determineOppToKill(ai, sa, source, dmg) != null) {
|
||||
// we already know we can kill a player, so go for it
|
||||
return true;
|
||||
@@ -138,7 +137,7 @@ public class DamageAllAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
int minGain = 200; // The minimum gain in destroyed creatures
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().isReusuableResource()) {
|
||||
if (sa.getPayCosts().isReusuableResource()) {
|
||||
if (computerList.isEmpty()) {
|
||||
minGain = 10; // nothing to lose
|
||||
// no creatures to lose and player can be damaged
|
||||
|
||||
@@ -48,7 +48,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
if (damage.equals("X") && sa.getSVar(damage).equals("Count$ChosenNumber")) {
|
||||
int energy = ai.getCounters(CounterType.ENERGY);
|
||||
int energy = ai.getCounters(CounterEnumType.ENERGY);
|
||||
for (SpellAbility s : source.getSpellAbilities()) {
|
||||
if ("PayEnergy".equals(s.getParam("AILogic"))) {
|
||||
energy += AbilityUtils.calculateAmount(source, s.getParam("CounterNum"), sa);
|
||||
@@ -165,7 +165,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
List<Card> wolves = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), "Creature.Wolf+untapped+YouCtrl+Other", ai, source);
|
||||
dmg = Aggregates.sum(wolves, CardPredicates.Accessors.fnGetNetPower);
|
||||
} else if ("Triskelion".equals(logic)) {
|
||||
final int n = source.getCounters(CounterType.P1P1);
|
||||
final int n = source.getCounters(CounterEnumType.P1P1);
|
||||
if (n > 0) {
|
||||
if (ComputerUtil.playImmediately(ai, sa)) {
|
||||
/*
|
||||
@@ -198,7 +198,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
if (sourceName.equals("Sorin, Grim Nemesis")) {
|
||||
int loyalty = source.getCounters(CounterType.LOYALTY);
|
||||
int loyalty = source.getCounters(CounterEnumType.LOYALTY);
|
||||
for (; loyalty > 0; loyalty--) {
|
||||
if (this.damageTargetAI(ai, sa, loyalty, false)) {
|
||||
dmg = ComputerUtilCombat.getEnoughDamageToKill(sa.getTargetCard(), loyalty, source, false, false);
|
||||
@@ -285,7 +285,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
if ("XCountersDamage".equals(logic) && sa.getPayCosts() != null) {
|
||||
if ("XCountersDamage".equals(logic)) {
|
||||
// Check to ensure that we have enough counters to remove per the defined PayX
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
@@ -445,11 +445,11 @@ public class DamageDealAi extends DamageAiBase {
|
||||
// As of right now, ranks planeswalkers by their Current Loyalty * 10 + Big buff if close to "Ultimate"
|
||||
int bestScore = 0;
|
||||
for (Card pw : pws) {
|
||||
int curLoyalty = pw.getCounters(CounterType.LOYALTY);
|
||||
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||
int pwScore = curLoyalty * 10;
|
||||
|
||||
for (SpellAbility sa : pw.getSpellAbilities()) {
|
||||
if (sa.hasParam("Ultimate") && sa.getPayCosts() != null) {
|
||||
if (sa.hasParam("Ultimate")) {
|
||||
Integer loyaltyCost = 0;
|
||||
CostRemoveCounter remLoyalty = sa.getPayCosts().getCostPartByType(CostRemoveCounter.class);
|
||||
if (remLoyalty != null) {
|
||||
@@ -478,7 +478,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
|
||||
int bestScore = Integer.MAX_VALUE;
|
||||
for (Card pw : pws) {
|
||||
int curLoyalty = pw.getCounters(CounterType.LOYALTY);
|
||||
int curLoyalty = pw.getCounters(CounterEnumType.LOYALTY);
|
||||
|
||||
if (curLoyalty < bestScore) {
|
||||
bestScore = curLoyalty;
|
||||
@@ -794,8 +794,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
if (((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai))
|
||||
|| (SpellAbilityAi.isSorcerySpeed(sa) && phase.is(PhaseType.MAIN2))
|
||||
|| ("PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai))
|
||||
|| sa.getPayCosts() == null || immediately
|
||||
|| this.shouldTgtP(ai, sa, dmg, noPrevention)) &&
|
||||
|| immediately || shouldTgtP(ai, sa, dmg, noPrevention)) &&
|
||||
(!avoidTargetP(ai, sa))) {
|
||||
tcs.add(enemy);
|
||||
if (divided) {
|
||||
@@ -1126,8 +1125,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
continue;
|
||||
}
|
||||
// currently works only with cards that don't have additional costs (only mana is supported)
|
||||
if (ab.getPayCosts() != null
|
||||
&& (ab.getPayCosts().hasNoManaCost() || ab.getPayCosts().hasOnlySpecificCostType(CostPartMana.class))) {
|
||||
if (ab.getPayCosts().hasNoManaCost() || ab.getPayCosts().hasOnlySpecificCostType(CostPartMana.class)) {
|
||||
String dmgDef = "0";
|
||||
if (ab.getApi() == ApiType.DealDamage) {
|
||||
dmgDef = ab.getParamOrDefault("NumDmg", "0");
|
||||
@@ -1151,7 +1149,7 @@ public class DamageDealAi extends DamageAiBase {
|
||||
}
|
||||
|
||||
// FIXME: should it also check restrictions for targeting players?
|
||||
ManaCost costSa = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : ManaCost.NO_COST;
|
||||
ManaCost costSa = sa.getPayCosts().getTotalMana();
|
||||
ManaCost costAb = ab.getPayCosts().getTotalMana(); // checked for null above
|
||||
ManaCost total = ManaCost.combine(costSa, costAb);
|
||||
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
|
||||
|
||||
@@ -101,7 +101,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
||||
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
|
||||
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
||||
if (source.getCounters(CounterType.LOYALTY) < minLoyalty) {
|
||||
if (source.getCounters(CounterEnumType.LOYALTY) < minLoyalty) {
|
||||
return false;
|
||||
}
|
||||
} else if ("Polymorph".equals(logic)) {
|
||||
@@ -161,7 +161,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
//Check for undying
|
||||
return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterType.P1P1) > 0);
|
||||
return (!c.hasKeyword(Keyword.UNDYING) || c.getCounters(CounterEnumType.P1P1) > 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -222,6 +222,10 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
Card choice = null;
|
||||
// If the targets are only of one type, take the best
|
||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||
if ("Pongify".equals(logic)) {
|
||||
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
||||
}
|
||||
|
||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||
if ("OppDestroyYours".equals(logic)) {
|
||||
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
||||
@@ -229,19 +233,6 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ("Pongify".equals(logic)) {
|
||||
final Card token = TokenAi.spawnToken(choice.getController(), sa.getSubAbility());
|
||||
if (token == null) {
|
||||
return true; // becomes Terminate
|
||||
} else {
|
||||
if (source.getGame().getPhaseHandler().getPhase()
|
||||
.isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) || // prevent surprise combatant
|
||||
ComputerUtilCard.evaluateCreature(choice) < 1.5
|
||||
* ComputerUtilCard.evaluateCreature(token)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (CardLists.getNotType(list, "Land").isEmpty()) {
|
||||
choice = ComputerUtilCard.getBestLandAI(list);
|
||||
|
||||
@@ -256,7 +247,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
}
|
||||
//option to hold removal instead only applies for single targeted removal
|
||||
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
||||
if (!ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -277,6 +268,7 @@ public class DestroyAi extends SpellAbilityAi {
|
||||
SpellAbility sp = aura.getFirstSpellAbility();
|
||||
if (sp != null && "GainControl".equals(sp.getParam("AILogic"))
|
||||
&& aura.getController() != ai && sa.canTarget(aura)) {
|
||||
list.remove(choice);
|
||||
choice = aura;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
public boolean chkAIDrawback(SpellAbility sa, Player aiPlayer) {
|
||||
//TODO: Check for bad outcome
|
||||
return true;
|
||||
return doMassRemovalLogic(aiPlayer, sa);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
@@ -30,6 +32,10 @@ public class DigAi extends SpellAbilityAi {
|
||||
final Card host = sa.getHostCard();
|
||||
Player libraryOwner = ai;
|
||||
|
||||
if (!willPayCosts(ai, sa, sa.getPayCosts(), host)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sa.usesTargeting()) {
|
||||
sa.resetTargets();
|
||||
if (!opp.canBeTargetedBy(sa)) {
|
||||
@@ -132,7 +138,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer) {
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> valid, boolean isOptional, Player relatedPlayer, Map<String, Object> params) {
|
||||
if ("DigForCreature".equals(sa.getParam("AILogic"))) {
|
||||
Card bestChoice = ComputerUtilCard.getBestCreatureAI(valid);
|
||||
if (bestChoice == null) {
|
||||
@@ -163,7 +169,7 @@ public class DigAi extends SpellAbilityAi {
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
public Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
// an opponent choose a card from
|
||||
return Iterables.getFirst(options, null);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.*;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
@@ -262,22 +263,20 @@ public class DrawAi extends SpellAbilityAi {
|
||||
// Draw up to max hand size but leave at least 3 in library
|
||||
numCards = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
|
||||
|
||||
if (sa.getPayCosts() != null) {
|
||||
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
|
||||
// [Necrologia, Pay X Life : Draw X Cards]
|
||||
// Don't draw more than what's "safe" and don't risk a near death experience
|
||||
// Maybe would be better to check for "serious danger" and take more risk?
|
||||
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
|
||||
numCards--;
|
||||
}
|
||||
} else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||
// [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
|
||||
// TODO: Add special logic to limit/otherwise modify the ChosenX value here
|
||||
if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) {
|
||||
// [Necrologia, Pay X Life : Draw X Cards]
|
||||
// Don't draw more than what's "safe" and don't risk a near death experience
|
||||
// Maybe would be better to check for "serious danger" and take more risk?
|
||||
while ((ComputerUtil.aiLifeInDanger(ai, false, numCards) && (numCards > 0))) {
|
||||
numCards--;
|
||||
}
|
||||
} else if (sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) {
|
||||
// [e.g. Krav, the Unredeemed and other cases which say "Sacrifice X creatures: draw X cards]
|
||||
// TODO: Add special logic to limit/otherwise modify the ChosenX value here
|
||||
|
||||
// Skip this ability if nothing is to be chosen for sacrifice
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
// Skip this ability if nothing is to be chosen for sacrifice
|
||||
if (numCards <= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +349,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
// try to make opponent lose to poison
|
||||
// currently only Caress of Phyrexia
|
||||
if (getPoison != null && oppA.canReceiveCounters(CounterType.POISON)) {
|
||||
if (getPoison != null && oppA.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (oppA.getPoisonCounters() + numCards > 9) {
|
||||
sa.getTargets().add(oppA);
|
||||
return true;
|
||||
@@ -394,7 +393,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (getPoison != null && ai.canReceiveCounters(CounterType.POISON)) {
|
||||
if (getPoison != null && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (numCards + ai.getPoisonCounters() >= 8) {
|
||||
aiTarget = false;
|
||||
}
|
||||
@@ -453,7 +452,7 @@ public class DrawAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
// ally would lose because of poison
|
||||
if (getPoison != null && ally.canReceiveCounters(CounterType.POISON)) {
|
||||
if (getPoison != null && ally.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
if (ally.getPoisonCounters() + numCards > 9) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class EffectAi extends SpellAbilityAi {
|
||||
} else if (logic.equals("SpellCopy")) {
|
||||
// fetch Instant or Sorcery and AI has reason to play this turn
|
||||
// does not try to get itself
|
||||
final ManaCost costSa = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : ManaCost.NO_COST;
|
||||
final ManaCost costSa = sa.getPayCosts().getTotalMana();
|
||||
final int count = CardLists.count(ai.getCardsIn(ZoneType.Hand), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
@@ -135,7 +135,7 @@ public class EffectAi extends SpellAbilityAi {
|
||||
AiPlayDecision decision = ((PlayerControllerAi)ai.getController()).getAi().canPlaySa(ab);
|
||||
// see if we can pay both for this spell and for the Effect spell we're considering
|
||||
if (decision == AiPlayDecision.WillPlay || decision == AiPlayDecision.WaitForMain2) {
|
||||
ManaCost costAb = ab.getPayCosts() != null ? ab.getPayCosts().getTotalMana() : ManaCost.NO_COST;
|
||||
ManaCost costAb = ab.getPayCosts().getTotalMana();
|
||||
ManaCost total = ManaCost.combine(costSa, costAb);
|
||||
SpellAbility combinedAb = ab.copyWithDefinedCost(new Cost(total, false));
|
||||
// can we pay both costs?
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
@@ -84,7 +85,7 @@ public final class EncodeAi extends SpellAbilityAi {
|
||||
* forge.game.player.Player)
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
public Card chooseSingleCard(final Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
return chooseCard(ai, options, isOptional);
|
||||
}
|
||||
|
||||
|
||||
27
forge-ai/src/main/java/forge/ai/ability/InvestigateAi.java
Normal file
27
forge-ai/src/main/java/forge/ai/ability/InvestigateAi.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
public class InvestigateAi extends SpellAbilityAi {
|
||||
/* (non-Javadoc)
|
||||
* @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility)
|
||||
*/
|
||||
@Override
|
||||
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
|
||||
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||
|
||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
|
||||
@@ -23,7 +25,7 @@ public class LegendaryRuleAi extends SpellAbilityAi {
|
||||
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
// Choose a single legendary/planeswalker card to keep
|
||||
Card firstOption = Iterables.getFirst(options, null);
|
||||
boolean choosingFromPlanewalkers = firstOption.isPlaneswalker();
|
||||
@@ -38,16 +40,16 @@ public class LegendaryRuleAi extends SpellAbilityAi {
|
||||
if (firstOption.getName().equals("Dark Depths")) {
|
||||
Card best = firstOption;
|
||||
for (Card c : options) {
|
||||
if (c.getCounters(CounterType.ICE) < best.getCounters(CounterType.ICE)) {
|
||||
if (c.getCounters(CounterEnumType.ICE) < best.getCounters(CounterEnumType.ICE)) {
|
||||
best = c;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
} else if (firstOption.getCounters(CounterType.KI) > 0) {
|
||||
} else if (firstOption.getCounters(CounterEnumType.KI) > 0) {
|
||||
// Extra Rule for KI counter
|
||||
Card best = firstOption;
|
||||
for (Card c : options) {
|
||||
if (c.getCounters(CounterType.KI) > best.getCounters(CounterType.KI)) {
|
||||
if (c.getCounters(CounterEnumType.KI) > best.getCounters(CounterEnumType.KI)) {
|
||||
best = c;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,6 @@ public class LifeGainAi extends SpellAbilityAi {
|
||||
if (lifeCritical
|
||||
&& sa.isAbility()
|
||||
&& sa.getHostCard() != null && sa.getHostCard().isCreature()
|
||||
&& sa.getPayCosts() != null
|
||||
&& (sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class) || sa.getPayCosts().hasSpecificCostType(CostSacrifice.class))) {
|
||||
if (!game.getStack().isEmpty()) {
|
||||
SpellAbility saTop = game.getStack().peekAbility();
|
||||
|
||||
@@ -5,7 +5,7 @@ import forge.ai.ComputerUtilMana;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -130,7 +130,7 @@ public class LifeSetAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (sourceName.equals("Eternity Vessel")
|
||||
&& (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterType.CHARGE) == 0))) {
|
||||
&& (opponent.isCardInPlay("Vampire Hexmage") || (source.getCounters(CounterEnumType.CHARGE) == 0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
return true; // handled elsewhere, does not meet the standard requirements
|
||||
}
|
||||
|
||||
return sa.getPayCosts() != null && sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
|
||||
return sa.getPayCosts().hasNoManaCost() && sa.getPayCosts().isReusuableResource()
|
||||
&& sa.getSubAbility() == null && ComputerUtil.playImmediately(ai, sa);
|
||||
// return super.checkApiLogic(ai, sa);
|
||||
}
|
||||
@@ -119,8 +119,8 @@ public class ManaEffectAi extends SpellAbilityAi {
|
||||
int numCounters = 0;
|
||||
int manaSurplus = 0;
|
||||
if ("XChoice".equals(host.getSVar("X"))
|
||||
&& sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
||||
CounterType ctrType = CounterType.KI; // Petalmane Baku
|
||||
&& sa.getPayCosts().hasSpecificCostType(CostRemoveCounter.class)) {
|
||||
CounterType ctrType = CounterType.get(CounterEnumType.KI); // Petalmane Baku
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
ctrType = ((CostRemoveCounter)part).counter;
|
||||
|
||||
@@ -18,6 +18,7 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MustBlockAi extends SpellAbilityAi {
|
||||
|
||||
@@ -167,7 +168,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional,
|
||||
Player targetedPlayer) {
|
||||
Player targetedPlayer, Map<String, Object> params) {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
Card attacker = host;
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
||||
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.ai.SpellApiToAi;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerActionConfirmMode;
|
||||
@@ -23,10 +24,17 @@ public class PeekAndRevealAi extends SpellAbilityAi {
|
||||
if (sa instanceof AbilityStatic) {
|
||||
return false;
|
||||
}
|
||||
if ("Main2".equals(sa.getParam("AILogic"))) {
|
||||
|
||||
String logic = sa.getParamOrDefault("AILogic", "");
|
||||
if ("Main2".equals(logic)) {
|
||||
if (aiPlayer.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("EndOfOppTurn".equals(logic)) {
|
||||
PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||
if (!(ph.getNextTurn() == aiPlayer && ph.is(PhaseType.END_OF_TURN))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// So far this only appears on Triggers, but will expand
|
||||
// once things get converted from Dig + NoMove
|
||||
|
||||
@@ -20,6 +20,7 @@ import forge.game.zone.ZoneType;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PlayAi extends SpellAbilityAi {
|
||||
|
||||
@@ -84,11 +85,11 @@ public class PlayAi extends SpellAbilityAi {
|
||||
return ComputerUtil.targetPlayableSpellCard(ai, cards, sa, sa.hasParam("WithoutManaCost"));
|
||||
} else if (logic.startsWith("NeedsChosenCard")) {
|
||||
int minCMC = 0;
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null) {
|
||||
minCMC = sa.getPayCosts().getCostMana().getMana().getCMC();
|
||||
if (sa.getPayCosts().getCostMana() != null) {
|
||||
minCMC = sa.getPayCosts().getTotalMana().getCMC();
|
||||
}
|
||||
validOpts = CardLists.filter(validOpts, CardPredicates.greaterCMC(minCMC));
|
||||
return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null) != null;
|
||||
return chooseSingleCard(ai, sa, validOpts, sa.hasParam("Optional"), null, null) != null;
|
||||
}
|
||||
|
||||
if (source != null && source.hasKeyword(Keyword.HIDEAWAY) && source.hasRemembered()) {
|
||||
@@ -142,8 +143,7 @@ public class PlayAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
public Card chooseSingleCard(final Player ai, final SpellAbility sa, Iterable<Card> options,
|
||||
final boolean isOptional,
|
||||
Player targetedPlayer) {
|
||||
final boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
List<Card> tgtCards = CardLists.filter(options, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
@@ -156,9 +156,7 @@ public class PlayAi extends SpellAbilityAi {
|
||||
if (sa.hasParam("WithoutManaCost")) {
|
||||
// Try to avoid casting instants and sorceries with X in their cost, since X will be assumed to be 0.
|
||||
if (!(spell instanceof SpellPermanent)) {
|
||||
if (spell.getPayCosts() != null
|
||||
&& spell.getPayCosts().getCostMana() != null
|
||||
&& spell.getPayCosts().getCostMana().getMana().countX() > 0) {
|
||||
if (spell.getPayCosts().getTotalMana().countX() > 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.google.common.base.Predicate;
|
||||
import forge.ai.ComputerUtil;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
@@ -59,7 +60,7 @@ public class PoisonAi extends SpellAbilityAi {
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
if (sa.usesTargeting()) {
|
||||
return tgtPlayer(ai, sa, mandatory);
|
||||
} else if (mandatory || !ai.canReceiveCounters(CounterType.POISON)) {
|
||||
} else if (mandatory || !ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
// mandatory or ai is uneffected
|
||||
return true;
|
||||
} else {
|
||||
@@ -92,7 +93,7 @@ public class PoisonAi extends SpellAbilityAi {
|
||||
public boolean apply(Player input) {
|
||||
if (input.cantLose()) {
|
||||
return false;
|
||||
} else if (!input.canReceiveCounters(CounterType.POISON)) {
|
||||
} else if (!input.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -113,7 +114,7 @@ public class PoisonAi extends SpellAbilityAi {
|
||||
if (tgts.isEmpty()) {
|
||||
if (mandatory) {
|
||||
// AI is uneffected
|
||||
if (ai.canBeTargetedBy(sa) && ai.canReceiveCounters(CounterType.POISON)) {
|
||||
if (ai.canBeTargetedBy(sa) && ai.canReceiveCounters(CounterType.get(CounterEnumType.POISON))) {
|
||||
sa.getTargets().add(ai);
|
||||
return true;
|
||||
}
|
||||
@@ -127,7 +128,7 @@ public class PoisonAi extends SpellAbilityAi {
|
||||
if (input.cantLose()) {
|
||||
return true;
|
||||
}
|
||||
return !input.canReceiveCounters(CounterType.POISON);
|
||||
return !input.canReceiveCounters(CounterType.get(CounterEnumType.POISON));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -202,7 +202,7 @@ public class ProtectAi extends SpellAbilityAi {
|
||||
if (game.getStack().isEmpty()) {
|
||||
// If the cost is tapping, don't activate before declare
|
||||
// attack/block
|
||||
if ((sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.getPayCosts().hasTapCost()) {
|
||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||
list.remove(sa.getHostCard());
|
||||
|
||||
@@ -9,7 +9,6 @@ import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
import forge.game.cost.CostRemoveCounter;
|
||||
@@ -23,7 +22,6 @@ import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Aggregates;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -71,9 +69,9 @@ public class PumpAi extends PumpAiBase {
|
||||
return false;
|
||||
}
|
||||
} else if ("Aristocrat".equals(aiLogic)) {
|
||||
return doAristocratLogic(sa, ai);
|
||||
return SpecialAiLogic.doAristocratLogic(ai, sa);
|
||||
} else if (aiLogic.startsWith("AristocratCounters")) {
|
||||
return doAristocratWithCountersLogic(sa, ai);
|
||||
return SpecialAiLogic.doAristocratWithCountersLogic(ai, sa);
|
||||
} else if ("RiskFactor".equals(aiLogic)) {
|
||||
if (ai.getCardsIn(ZoneType.Hand).size() + 3 >= ai.getMaxHandSize()) {
|
||||
return false;
|
||||
@@ -150,7 +148,7 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
|
||||
final String counterType = moveSA.getParam("CounterType");
|
||||
final CounterType cType = "Any".equals(counterType) ? null : CounterType.valueOf(counterType);
|
||||
final CounterType cType = "Any".equals(counterType) ? null : CounterType.getType(counterType);
|
||||
|
||||
final PhaseHandler ph = game.getPhaseHandler();
|
||||
if (ph.inCombat() && ph.getPlayerTurn().isOpponentOf(ai)) {
|
||||
@@ -185,7 +183,7 @@ public class PumpAi extends PumpAiBase {
|
||||
// cant use substract on Copy
|
||||
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
||||
|
||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||
if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|
||||
|| card.isToken();
|
||||
}
|
||||
@@ -235,7 +233,7 @@ public class PumpAi extends PumpAiBase {
|
||||
// cant use substract on Copy
|
||||
srcCardCpy.setCounters(cType, srcCardCpy.getCounters(cType) - amount);
|
||||
|
||||
if (CounterType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||
if (CounterEnumType.P1P1.equals(cType) && srcCardCpy.getNetToughness() <= 0) {
|
||||
return srcCardCpy.getCounters(cType) > 0 || !card.hasKeyword(Keyword.UNDYING)
|
||||
|| card.isToken();
|
||||
}
|
||||
@@ -388,7 +386,7 @@ public class PumpAi extends PumpAiBase {
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (grantsUsefulExtraBlockOpts(ai, card)) {
|
||||
} else if (grantsUsefulExtraBlockOpts(ai, sa, card, keywords)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -402,7 +400,7 @@ public class PumpAi extends PumpAiBase {
|
||||
|
||||
if ("DebuffForXCounters".equals(sa.getParam("AILogic")) && sa.getTargetCard() != null) {
|
||||
// e.g. Skullmane Baku
|
||||
CounterType ctrType = CounterType.KI;
|
||||
CounterType ctrType = CounterType.get(CounterEnumType.KI);
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
ctrType = ((CostRemoveCounter)part).counter;
|
||||
@@ -515,7 +513,7 @@ public class PumpAi extends PumpAiBase {
|
||||
if (game.getStack().isEmpty()) {
|
||||
// If the cost is tapping, don't activate before declare
|
||||
// attack/block
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.getPayCosts().hasTapCost()) {
|
||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||
list.remove(sa.getHostCard());
|
||||
@@ -730,7 +728,7 @@ public class PumpAi extends PumpAiBase {
|
||||
final String numAttack = sa.hasParam("NumAtt") ? sa.getParam("NumAtt") : "";
|
||||
|
||||
if (numDefense.equals("-X") && sa.getSVar("X").equals("Count$ChosenNumber")) {
|
||||
int energy = ai.getCounters(CounterType.ENERGY);
|
||||
int energy = ai.getCounters(CounterEnumType.ENERGY);
|
||||
for (SpellAbility s : source.getSpellAbilities()) {
|
||||
if ("PayEnergy".equals(s.getParam("AILogic"))) {
|
||||
energy += AbilityUtils.calculateAmount(source, s.getParam("CounterNum"), sa);
|
||||
@@ -794,256 +792,4 @@ public class PumpAi extends PumpAiBase {
|
||||
//and the pump isn't mandatory
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean doAristocratLogic(final SpellAbility sa, final Player ai) {
|
||||
// A logic for cards that say "Sacrifice a creature: CARDNAME gets +X/+X until EOT"
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final Card source = sa.getHostCard();
|
||||
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
|
||||
final int powerBonus = sa.hasParam("NumAtt") ? AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa) : 0;
|
||||
final int toughnessBonus = sa.hasParam("NumDef") ? AbilityUtils.calculateAmount(source, sa.getParam("NumDef"), sa) : 0;
|
||||
final boolean indestructible = sa.hasParam("KW") && sa.getParam("KW").contains("Indestructible");
|
||||
final int selfEval = ComputerUtilCard.evaluateCreature(source);
|
||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
|
||||
|
||||
if (numOtherCreats == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to save the card from death by pumping it if it's threatened with a damage spell
|
||||
if (isThreatened && (toughnessBonus > 0 || indestructible)) {
|
||||
SpellAbility saTop = game.getStack().peekAbility();
|
||||
|
||||
if (saTop.getApi() == ApiType.DealDamage || saTop.getApi() == ApiType.DamageAll) {
|
||||
int dmg = AbilityUtils.calculateAmount(saTop.getHostCard(), saTop.getParam("NumDmg"), saTop) + source.getDamage();
|
||||
final int numCreatsToSac = indestructible ? 1 : Math.max(1, (int)Math.ceil((dmg - source.getNetToughness() + 1) / toughnessBonus));
|
||||
|
||||
if (numCreatsToSac > 1) { // probably not worth sacrificing too much
|
||||
return false;
|
||||
}
|
||||
|
||||
if (indestructible || (source.getNetToughness() <= dmg && source.getNetToughness() + toughnessBonus * numCreatsToSac > dmg)) {
|
||||
final CardCollection sacFodder = CardLists.filter(ai.getCreaturesInPlay(),
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| card.hasSVar("SacMe")
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval; // Maybe around 150 is OK?
|
||||
}
|
||||
}
|
||||
);
|
||||
return sacFodder.size() >= numCreatsToSac;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (combat == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (combat.isAttacking(source)) {
|
||||
if (combat.getBlockers(source).isEmpty()) {
|
||||
// Unblocked. Check if able to deal lethal, then sac'ing everything is fair game if
|
||||
// the opponent is tapped out or if we're willing to risk it (will currently risk it
|
||||
// in case it sacs less than half its creatures to deal lethal damage)
|
||||
|
||||
// TODO: also teach the AI to account for Trample, but that's trickier (needs to account fully
|
||||
// for potential damage prevention, various effects like reducing damage to 0, etc.)
|
||||
|
||||
final Player defPlayer = combat.getDefendingPlayerRelatedTo(source);
|
||||
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;
|
||||
|
||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT); // Flesh-Eater Imp
|
||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||
|
||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.POISON)) {
|
||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||
}
|
||||
|
||||
final int numCreatsToSac = indestructible ? 1 : (lethalDmg - source.getNetCombatDamage()) / (powerBonus != 0 ? powerBonus : 1);
|
||||
|
||||
if (defTappedOut || numCreatsToSac < numOtherCreats / 2) {
|
||||
return source.getNetCombatDamage() < lethalDmg
|
||||
&& source.getNetCombatDamage() + numOtherCreats * powerBonus >= lethalDmg;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
|
||||
// than the card we attacked with.
|
||||
final CardCollection sacTgts = CardLists.filter(ai.getCreaturesInPlay(),
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (sacTgts.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness);
|
||||
final int DefP = indestructible ? 0 : Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
|
||||
|
||||
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
|
||||
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
|
||||
}
|
||||
} else {
|
||||
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
|
||||
final CardCollection sacFodder = CardLists.filter(ai.getCreaturesInPlay(),
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| card.hasSVar("SacMe")
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval; // Maybe around 150 is OK?
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return !sacFodder.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean doAristocratWithCountersLogic(final SpellAbility sa, final Player ai) {
|
||||
// A logic for cards that say "Sacrifice a creature: put X +1/+1 counters on CARDNAME" (e.g. Falkenrath Aristocrat)
|
||||
final Card source = sa.getHostCard();
|
||||
final String logic = sa.getParam("AILogic"); // should not even get here unless there's an Aristocrats logic applied
|
||||
final boolean isDeclareBlockers = ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS);
|
||||
|
||||
final int numOtherCreats = Math.max(0, ai.getCreaturesInPlay().size() - 1);
|
||||
if (numOtherCreats == 0) {
|
||||
// Cut short if there's nothing to sac at all
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the standard Aristocrats logic applies first (if in the right conditions for it)
|
||||
final boolean isThreatened = ComputerUtil.predictThreatenedObjects(ai, null, true).contains(source);
|
||||
if (isDeclareBlockers || isThreatened) {
|
||||
if (doAristocratLogic(sa, ai)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if anything is to be gained from the PutCounter subability
|
||||
SpellAbility countersSa = null;
|
||||
if (sa.getSubAbility() == null || sa.getSubAbility().getApi() != ApiType.PutCounter) {
|
||||
if (sa.getApi() == ApiType.PutCounter) {
|
||||
// called directly from CountersPutAi
|
||||
countersSa = sa;
|
||||
}
|
||||
} else {
|
||||
countersSa = sa.getSubAbility();
|
||||
}
|
||||
|
||||
if (countersSa == null) {
|
||||
// Shouldn't get here if there is no PutCounter subability (wrong AI logic specified?)
|
||||
System.err.println("Warning: AILogic AristocratCounters was specified on " + source + ", but there was no PutCounter SA in chain!");
|
||||
return false;
|
||||
}
|
||||
|
||||
final Game game = ai.getGame();
|
||||
final Combat combat = game.getCombat();
|
||||
final int selfEval = ComputerUtilCard.evaluateCreature(source);
|
||||
|
||||
String typeToGainCtr = "";
|
||||
if (logic.contains(".")) {
|
||||
typeToGainCtr = logic.substring(logic.indexOf(".") + 1);
|
||||
}
|
||||
CardCollection relevantCreats = typeToGainCtr.isEmpty() ? ai.getCreaturesInPlay()
|
||||
: CardLists.filter(ai.getCreaturesInPlay(), CardPredicates.isType(typeToGainCtr));
|
||||
relevantCreats.remove(source);
|
||||
if (relevantCreats.isEmpty()) {
|
||||
// No relevant creatures to sac
|
||||
return false;
|
||||
}
|
||||
|
||||
int numCtrs = AbilityUtils.calculateAmount(source, countersSa.getParam("CounterNum"), countersSa);
|
||||
|
||||
if (combat != null && combat.isAttacking(source) && isDeclareBlockers) {
|
||||
if (combat.getBlockers(source).isEmpty()) {
|
||||
// Unblocked. Check if we can deal lethal after receiving counters.
|
||||
final Player defPlayer = combat.getDefendingPlayerRelatedTo(source);
|
||||
final boolean defTappedOut = ComputerUtilMana.getAvailableManaEstimate(defPlayer) == 0;
|
||||
|
||||
final boolean isInfect = source.hasKeyword(Keyword.INFECT);
|
||||
int lethalDmg = isInfect ? 10 - defPlayer.getPoisonCounters() : defPlayer.getLife();
|
||||
|
||||
if (isInfect && !combat.getDefenderByAttacker(source).canReceiveCounters(CounterType.POISON)) {
|
||||
lethalDmg = Integer.MAX_VALUE; // won't be able to deal poison damage to kill the opponent
|
||||
}
|
||||
|
||||
// Check if there's anything that will die anyway that can be eaten to gain a perma-bonus
|
||||
final CardCollection forcedSacTgts = CardLists.filter(relevantCreats,
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card)
|
||||
|| (combat.isAttacking(card) && combat.isBlocked(card) && ComputerUtilCombat.combatantWouldBeDestroyed(ai, card, combat));
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!forcedSacTgts.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final int numCreatsToSac = Math.max(0, (lethalDmg - source.getNetCombatDamage()) / numCtrs);
|
||||
|
||||
if (defTappedOut || numCreatsToSac < relevantCreats.size() / 2) {
|
||||
return source.getNetCombatDamage() < lethalDmg
|
||||
&& source.getNetCombatDamage() + relevantCreats.size() * numCtrs >= lethalDmg;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// We have already attacked. Thus, see if we have a creature to sac that is worse to lose
|
||||
// than the card we attacked with. Since we're getting a permanent bonus, consider sacrificing
|
||||
// things that are also threatened to be destroyed anyway.
|
||||
final CardCollection sacTgts = CardLists.filter(relevantCreats,
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| ComputerUtilCard.evaluateCreature(card) < selfEval
|
||||
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (sacTgts.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean sourceCantDie = ComputerUtilCombat.attackerCantBeDestroyedInCombat(ai, source);
|
||||
final int minDefT = Aggregates.min(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetToughness);
|
||||
final int DefP = sourceCantDie ? 0 : Aggregates.sum(combat.getBlockers(source), CardPredicates.Accessors.fnGetNetPower);
|
||||
|
||||
// Make sure we don't over-sacrifice, only sac until we can survive and kill a creature
|
||||
return source.getNetToughness() - source.getDamage() <= DefP || source.getNetCombatDamage() < minDefT;
|
||||
}
|
||||
} else {
|
||||
// We can't deal lethal, check if there's any sac fodder than can be used for other circumstances
|
||||
final boolean isBlocking = combat != null && combat.isBlocking(source);
|
||||
final CardCollection sacFodder = CardLists.filter(relevantCreats,
|
||||
new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return ComputerUtilCard.isUselessCreature(ai, card)
|
||||
|| card.hasSVar("SacMe")
|
||||
|| (isBlocking && ComputerUtilCard.evaluateCreature(card) < selfEval)
|
||||
|| ComputerUtil.predictThreatenedObjects(ai, null, true).contains(card);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return !sacFodder.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import forge.ai.ComputerUtilCombat;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.card.MagicColor;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
@@ -37,22 +38,46 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
|
||||
public boolean grantsUsefulExtraBlockOpts(final Player ai, final Card card) {
|
||||
public boolean grantsUsefulExtraBlockOpts(final Player ai, final SpellAbility sa, final Card card, List<String> keywords) {
|
||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
||||
Card pumped = ComputerUtilCard.getPumpedCreature(ai, sa, card, 0, 0, keywords);
|
||||
|
||||
if (ph.isPlayerTurn(ai) || !ph.getPhase().equals(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int canBlockNum = 1 + card.canBlockAdditional();
|
||||
int canBlockNumPumped = canBlockNum; // PumpedCreature doesn't return a meaningful value of canBlockAdditional, so we'll use sa params below
|
||||
|
||||
if (sa.hasParam("CanBlockAny")) {
|
||||
canBlockNumPumped = Integer.MAX_VALUE;
|
||||
} else if (sa.hasParam("CanBlockAmount")) {
|
||||
canBlockNumPumped += AbilityUtils.calculateAmount(pumped, sa.getParam("CanBlockAmount"), sa);
|
||||
}
|
||||
|
||||
int possibleBlockNum = 0;
|
||||
int possibleBlockNumPumped = 0;
|
||||
|
||||
for (Card attacker : ai.getGame().getCombat().getAttackers()) {
|
||||
if (CombatUtil.canBlock(attacker, card)) {
|
||||
possibleBlockNum++;
|
||||
if (possibleBlockNum > canBlockNum) {
|
||||
possibleBlockNum = canBlockNum;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return possibleBlockNum > canBlockNum;
|
||||
for (Card attacker : ai.getGame().getCombat().getAttackers()) {
|
||||
if (CombatUtil.canBlock(attacker, pumped)) {
|
||||
possibleBlockNumPumped++;
|
||||
if (possibleBlockNumPumped > canBlockNumPumped) {
|
||||
possibleBlockNumPumped = canBlockNumPumped;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return possibleBlockNumPumped > possibleBlockNum;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,7 +119,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c.equals(sa.getHostCard()) && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
||||
if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
|
||||
&& (combat == null || !combat.isAttacking(c))) {
|
||||
return false;
|
||||
}
|
||||
@@ -112,7 +137,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
|
||||
List<Card> attackers = CardLists.filter(ai.getCreaturesInPlay(), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
if (c.equals(sa.getHostCard()) && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()
|
||||
if (c.equals(sa.getHostCard()) && sa.getPayCosts().hasTapCost()
|
||||
&& (combat == null || !combat.isAttacking(c))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi {
|
||||
final PhaseHandler ph = aiPlayer.getGame().getPhaseHandler();
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
if (source.isPermanent() && sa.getRestrictions().isInstantSpeed() && sa.getPayCosts() != null
|
||||
if (source.isPermanent() && sa.getRestrictions().isInstantSpeed()
|
||||
&& (sa.getPayCosts().hasTapCost() || sa.getPayCosts().hasManaCost())) {
|
||||
// If it has an associated cost, try to only do this before own turn
|
||||
if (!(ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == aiPlayer)) {
|
||||
|
||||
@@ -6,9 +6,11 @@ import forge.ai.SpecialCardAi;
|
||||
import forge.ai.SpellAbilityAi;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
@@ -16,6 +18,7 @@ import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class RepeatEachAi extends SpellAbilityAi {
|
||||
@@ -47,21 +50,6 @@ public class RepeatEachAi extends SpellAbilityAi {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if ("GainControlOwns".equals(logic)) {
|
||||
List<Card> list = CardLists.filter(aiPlayer.getGame().getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card crd) {
|
||||
return crd.isCreature() && !crd.getController().equals(crd.getOwner());
|
||||
}
|
||||
});
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (final Card c : list) {
|
||||
if (aiPlayer.equals(c.getController())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if ("OpponentHasCreatures".equals(logic)) {
|
||||
for (Player opp : aiPlayer.getOpponents()) {
|
||||
if (!opp.getCreaturesInPlay().isEmpty()){
|
||||
@@ -108,8 +96,21 @@ public class RepeatEachAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
}
|
||||
// would not hit oppoent, don't do that
|
||||
// would not hit opponent, don't do that
|
||||
return hitOpp;
|
||||
} else if ("EquipAll".equals(logic)) {
|
||||
if (aiPlayer.getGame().getPhaseHandler().is(PhaseType.MAIN1, aiPlayer)) {
|
||||
final CardCollection unequipped = CardLists.filter(aiPlayer.getCardsIn(ZoneType.Battlefield), new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(Card card) {
|
||||
return card.isEquipment() && card.getAttachedTo() != sa.getHostCard();
|
||||
}
|
||||
});
|
||||
|
||||
return !unequipped.isEmpty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO Add some normal AI variability here
|
||||
@@ -118,7 +119,7 @@ public class RepeatEachAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer) {
|
||||
protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> options, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
return ComputerUtilCard.getBestCreatureAI(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,12 +46,10 @@ public class ScryAi extends SpellAbilityAi {
|
||||
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
|
||||
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
|
||||
// even if there's no mana cost.
|
||||
if (sa.getPayCosts() != null) {
|
||||
if (sa.getPayCosts().hasTapCost()
|
||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
if (sa.getPayCosts().hasTapCost()
|
||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
// AI logic to scry in Main 1 if there is no better option, otherwise scry at opponent's EOT
|
||||
@@ -76,8 +74,7 @@ public class ScryAi extends SpellAbilityAi {
|
||||
boolean hasSomethingElse = false;
|
||||
for (Card c : CardLists.filter(ai.getCardsIn(ZoneType.Hand), Predicates.not(CardPredicates.Presets.LANDS))) {
|
||||
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
||||
if (ab.getPayCosts() != null
|
||||
&& ab.getPayCosts().hasManaCost()
|
||||
if (ab.getPayCosts().hasManaCost()
|
||||
&& ComputerUtilMana.hasEnoughManaSourcesToCast(ab, ai)) {
|
||||
// TODO: currently looks for non-Scry cards, can most certainly be made smarter.
|
||||
if (ab.getApi() != ApiType.Scry) {
|
||||
@@ -102,7 +99,7 @@ public class ScryAi extends SpellAbilityAi {
|
||||
} else if ("BrainJar".equals(aiLogic)) {
|
||||
final Card source = sa.getHostCard();
|
||||
|
||||
int counterNum = source.getCounters(CounterType.CHARGE);
|
||||
int counterNum = source.getCounters(CounterEnumType.CHARGE);
|
||||
// no need for logic
|
||||
if (counterNum == 0) {
|
||||
return false;
|
||||
|
||||
@@ -174,7 +174,7 @@ public class SetStateAi extends SpellAbilityAi {
|
||||
if (!card.isFaceDown()) {
|
||||
transformed.turnFaceDown(true);
|
||||
} else {
|
||||
transformed.turnFaceUp(false, false);
|
||||
transformed.forceTurnFaceUp();
|
||||
}
|
||||
transformed.updateStateForView();
|
||||
return compareCards(card, transformed, ai, ph);
|
||||
@@ -248,7 +248,7 @@ public class SetStateAi extends SpellAbilityAi {
|
||||
final Card othercard = aiPlayer.getCardsIn(ZoneType.Battlefield, other.getName()).getFirst();
|
||||
|
||||
// for legendary KI counter creatures
|
||||
if (othercard.getCounters(CounterType.KI) >= source.getCounters(CounterType.KI)) {
|
||||
if (othercard.getCounters(CounterEnumType.KI) >= source.getCounters(CounterEnumType.KI)) {
|
||||
// if the other legendary is useless try to replace it
|
||||
return ComputerUtilCard.isUselessCreature(aiPlayer, othercard);
|
||||
}
|
||||
|
||||
@@ -47,12 +47,10 @@ public class SurveilAi extends SpellAbilityAi {
|
||||
// and right before the beginning of AI's turn, if possible, to avoid mana locking the AI and also to
|
||||
// try to scry right before drawing a card. Also, avoid tapping creatures in the AI's turn, if possible,
|
||||
// even if there's no mana cost.
|
||||
if (sa.getPayCosts() != null) {
|
||||
if (sa.getPayCosts().hasTapCost()
|
||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
if (sa.getPayCosts().hasTapCost()
|
||||
&& (sa.getPayCosts().hasManaCost() || (sa.getHostCard() != null && sa.getHostCard().isCreature()))
|
||||
&& !SpellAbilityAi.isSorcerySpeed(sa)) {
|
||||
return ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN);
|
||||
}
|
||||
|
||||
// in the player's turn Surveil should only be done in Main1 or in Upkeep if able
|
||||
|
||||
@@ -3,6 +3,7 @@ package forge.ai.ability;
|
||||
import forge.ai.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.cost.CostPart;
|
||||
@@ -68,7 +69,7 @@ public class TapAi extends TapAiBase {
|
||||
} else {
|
||||
if ("TapForXCounters".equals(sa.getParam("AILogic"))) {
|
||||
// e.g. Waxmane Baku
|
||||
CounterType ctrType = CounterType.KI;
|
||||
CounterType ctrType = CounterType.get(CounterEnumType.KI);
|
||||
for (CostPart part : sa.getPayCosts().getCostParts()) {
|
||||
if (part instanceof CostRemoveCounter) {
|
||||
ctrType = ((CostRemoveCounter)part).counter;
|
||||
|
||||
@@ -126,7 +126,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
for (final SpellAbility sa : c.getSpellAbilities()) {
|
||||
if (sa.isAbility() && sa.getPayCosts() != null && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package forge.ai.ability;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import forge.ai.*;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.ability.AbilityFactory;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.Card;
|
||||
@@ -25,16 +26,9 @@ import forge.game.player.PlayerActionConfirmMode;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.trigger.Trigger;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.PaperToken;
|
||||
import forge.util.MyRandom;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import forge.util.MyRandom;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -45,35 +39,10 @@ import java.util.List;
|
||||
* @version $Id: AbilityFactoryToken.java 17656 2012-10-22 19:32:56Z Max mtg $
|
||||
*/
|
||||
public class TokenAi extends SpellAbilityAi {
|
||||
private String tokenAmount;
|
||||
private String tokenPower;
|
||||
private String tokenToughness;
|
||||
|
||||
private Card actualToken;
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for AbilityFactory_Token.
|
||||
* </p>
|
||||
*
|
||||
* a {@link forge.game.ability.AbilityFactory} object.
|
||||
*/
|
||||
private void readParameters(final SpellAbility mapParams) {
|
||||
this.tokenAmount = mapParams.getParamOrDefault("TokenAmount", "1");
|
||||
|
||||
this.actualToken = TokenInfo.getProtoType(mapParams.getParam("TokenScript"), mapParams);
|
||||
|
||||
if (actualToken == null) {
|
||||
this.tokenPower = mapParams.getParam("TokenPower");
|
||||
this.tokenToughness = mapParams.getParam("TokenToughness");
|
||||
} else {
|
||||
this.tokenPower = actualToken.getBasePowerString();
|
||||
this.tokenToughness = actualToken.getBaseToughnessString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph) {
|
||||
readParameters(sa); // remember to call this somewhere!
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
// Planeswalker-related flags
|
||||
boolean pwMinus = false;
|
||||
@@ -96,21 +65,23 @@ public class TokenAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
}
|
||||
String tokenAmount = sa.getParamOrDefault("TokenAmount", "1");
|
||||
|
||||
if (actualToken == null) {
|
||||
actualToken = spawnToken(ai, sa);
|
||||
}
|
||||
Card actualToken = spawnToken(ai, sa);
|
||||
|
||||
if (actualToken == null) {
|
||||
if (actualToken == null || actualToken.getNetToughness() < 1) {
|
||||
final AbilitySub sub = sa.getSubAbility();
|
||||
// useful
|
||||
// no token created
|
||||
return pwPlus || (sub != null && SpellApiToAi.Converter.get(sub.getApi()).chkAIDrawback(sub, ai)); // planeswalker plus ability or sub-ability is
|
||||
}
|
||||
|
||||
String tokenPower = sa.getParamOrDefault("TokenPower", actualToken.getBasePowerString());
|
||||
String tokenToughness = sa.getParamOrDefault("TokenToughness", actualToken.getBaseToughnessString());
|
||||
|
||||
// X-cost spells
|
||||
if (this.tokenAmount.equals("X") || (this.tokenToughness != null && this.tokenToughness.equals("X"))) {
|
||||
int x = AbilityUtils.calculateAmount(sa.getHostCard(), this.tokenAmount, sa);
|
||||
if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) {
|
||||
int x = AbilityUtils.calculateAmount(sa.getHostCard(), tokenAmount, sa);
|
||||
if (source.getSVar("X").equals("Count$Converge")) {
|
||||
x = ComputerUtilMana.getConvergeCount(sa, ai);
|
||||
}
|
||||
@@ -124,14 +95,14 @@ public class TokenAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
if (canInterruptSacrifice(ai, sa, actualToken)) {
|
||||
if (canInterruptSacrifice(ai, sa, actualToken, tokenAmount)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean haste = this.actualToken.hasKeyword(Keyword.HASTE);
|
||||
boolean haste = actualToken.hasKeyword(Keyword.HASTE);
|
||||
boolean oneShot = sa.getSubAbility() != null
|
||||
&& sa.getSubAbility().getApi() == ApiType.DelayedTrigger;
|
||||
boolean isCreature = this.actualToken.getType().isCreature();
|
||||
boolean isCreature = actualToken.getType().isCreature();
|
||||
|
||||
// Don't generate tokens without haste before main 2 if possible
|
||||
if (ph.getPhase().isBefore(PhaseType.MAIN2) && ph.isPlayerTurn(ai) && !haste && !sa.hasParam("ActivationPhases")
|
||||
@@ -166,9 +137,10 @@ public class TokenAi extends SpellAbilityAi {
|
||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||
return false; // prevent infinite tokens?
|
||||
}
|
||||
Card actualToken = spawnToken(ai, sa);
|
||||
|
||||
// Don't kill AIs Legendary tokens
|
||||
if (this.actualToken.getType().isLegendary() && ai.isCardInPlay(this.actualToken.getName())) {
|
||||
if (actualToken.getType().isLegendary() && ai.isCardInPlay(actualToken.getName())) {
|
||||
// TODO Check if Token is useless due to an aura or counters?
|
||||
return false;
|
||||
}
|
||||
@@ -240,7 +212,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
/**
|
||||
* Checks if the token(s) can save a creature from a sacrifice effect
|
||||
*/
|
||||
private boolean canInterruptSacrifice(final Player ai, final SpellAbility sa, final Card token) {
|
||||
private boolean canInterruptSacrifice(final Player ai, final SpellAbility sa, final Card token, final String tokenAmount) {
|
||||
final Game game = ai.getGame();
|
||||
if (game.getStack().isEmpty()) {
|
||||
return false; // nothing to interrupt
|
||||
@@ -249,7 +221,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
if (topStack.getApi() != ApiType.Sacrifice) {
|
||||
return false; // not sacrifice effect
|
||||
}
|
||||
final int nTokens = AbilityUtils.calculateAmount(sa.getHostCard(), this.tokenAmount, sa);
|
||||
final int nTokens = AbilityUtils.calculateAmount(sa.getHostCard(), tokenAmount, sa);
|
||||
final String valid = topStack.getParamOrDefault("SacValid", "Card.Self");
|
||||
String num = sa.getParam("Amount");
|
||||
num = (num == null) ? "1" : num;
|
||||
@@ -271,7 +243,8 @@ public class TokenAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||
readParameters(sa);
|
||||
String tokenAmount = sa.getParamOrDefault("TokenAmount", "1");
|
||||
|
||||
final Card source = sa.getHostCard();
|
||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
||||
if (tgt != null) {
|
||||
@@ -282,8 +255,12 @@ public class TokenAi extends SpellAbilityAi {
|
||||
sa.getTargets().add(ai);
|
||||
}
|
||||
}
|
||||
if ("X".equals(this.tokenAmount) || "X".equals(this.tokenPower) || "X".equals(this.tokenToughness)) {
|
||||
int x = AbilityUtils.calculateAmount(source, this.tokenAmount, sa);
|
||||
Card actualToken = spawnToken(ai, sa);
|
||||
String tokenPower = sa.getParamOrDefault("TokenPower", actualToken.getBasePowerString());
|
||||
String tokenToughness = sa.getParamOrDefault("TokenToughness", actualToken.getBaseToughnessString());
|
||||
|
||||
if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) {
|
||||
int x = AbilityUtils.calculateAmount(source, tokenAmount, sa);
|
||||
if (source.getSVar("X").equals("Count$xPaid")) {
|
||||
// Set PayX here to maximum value.
|
||||
x = ComputerUtilMana.determineLeftoverMana(sa, ai);
|
||||
@@ -321,9 +298,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayer(forge.game.player.Player, forge.card.spellability.SpellAbility, Iterable<forge.game.player.Player> options)
|
||||
*/
|
||||
@Override
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options) {
|
||||
// TODO: AILogic
|
||||
readParameters(sa); // remember to call this somewhere!
|
||||
protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable<Player> options, Map<String, Object> params) {
|
||||
Combat combat = ai.getGame().getCombat();
|
||||
// TokenAttacking
|
||||
if (combat != null && sa.hasParam("TokenAttacking")) {
|
||||
@@ -341,9 +316,7 @@ public class TokenAi extends SpellAbilityAi {
|
||||
* @see forge.card.ability.SpellAbilityAi#chooseSinglePlayerOrPlaneswalker(forge.game.player.Player, forge.card.spellability.SpellAbility, Iterable<forge.game.GameEntity> options)
|
||||
*/
|
||||
@Override
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options) {
|
||||
// TODO: AILogic
|
||||
readParameters(sa); // remember to call this somewhere!
|
||||
protected GameEntity chooseSinglePlayerOrPlaneswalker(Player ai, SpellAbility sa, Iterable<GameEntity> options, Map<String, Object> params) {
|
||||
Combat combat = ai.getGame().getCombat();
|
||||
// TokenAttacking
|
||||
if (combat != null && sa.hasParam("TokenAttacking")) {
|
||||
@@ -374,154 +347,22 @@ public class TokenAi extends SpellAbilityAi {
|
||||
* @param sa Token SpellAbility
|
||||
* @return token creature created by ability
|
||||
*/
|
||||
@Deprecated
|
||||
public static Card spawnToken(Player ai, SpellAbility sa) {
|
||||
return spawnToken(ai, sa, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the token as a Card object.
|
||||
* @param ai owner of the new token
|
||||
* @param sa Token SpellAbility
|
||||
* @param notNull if the token would not survive, still return it
|
||||
* @return token creature created by ability
|
||||
*/
|
||||
// TODO Is this just completely copied from TokenEffect? Let's just call that thing
|
||||
@Deprecated
|
||||
public static Card spawnToken(Player ai, SpellAbility sa, boolean notNull) {
|
||||
final Card host = sa.getHostCard();
|
||||
|
||||
if (!sa.hasParam("TokenScript")) {
|
||||
throw new RuntimeException("Spell Ability has no TokenScript: " + sa);
|
||||
}
|
||||
Card result = TokenInfo.getProtoType(sa.getParam("TokenScript"), sa);
|
||||
|
||||
if (result != null) {
|
||||
result.setController(ai, 0);
|
||||
return result;
|
||||
if (result == null) {
|
||||
throw new RuntimeException("don't find Token for TokenScript: " + sa.getParam("TokenScript"));
|
||||
}
|
||||
|
||||
String[] tokenKeywords = sa.hasParam("TokenKeywords") ? sa.getParam("TokenKeywords").split("<>") : new String[0];
|
||||
String tokenPower = sa.getParam("TokenPower");
|
||||
String tokenToughness = sa.getParam("TokenToughness");
|
||||
String tokenName = sa.getParam("TokenName");
|
||||
String[] tokenTypes = sa.getParam("TokenTypes").split(",");
|
||||
StringBuilder cost = new StringBuilder();
|
||||
String[] tokenColors = sa.getParam("TokenColors").split(",");
|
||||
String tokenImage = sa.hasParam("TokenImage") ? PaperToken.makeTokenFileName(sa.getParam("TokenImage")) : "";
|
||||
String[] tokenAbilities = sa.hasParam("TokenAbilities") ? sa.getParam("TokenAbilities").split(",") : null;
|
||||
String[] tokenTriggers = sa.hasParam("TokenTriggers") ? sa.getParam("TokenTriggers").split(",") : null;
|
||||
String[] tokenSVars = sa.hasParam("TokenSVars") ? sa.getParam("TokenSVars").split(",") : null;
|
||||
String[] tokenStaticAbilities = sa.hasParam("TokenStaticAbilities") ? sa.getParam("TokenStaticAbilities").split(",") : null;
|
||||
String[] tokenHiddenKeywords = sa.hasParam("TokenHiddenKeywords") ? sa.getParam("TokenHiddenKeywords").split("&") : null;
|
||||
final String[] substitutedColors = Arrays.copyOf(tokenColors, tokenColors.length);
|
||||
for (int i = 0; i < substitutedColors.length; i++) {
|
||||
if (substitutedColors[i].equals("ChosenColor")) {
|
||||
// this currently only supports 1 chosen color
|
||||
substitutedColors[i] = host.getChosenColor();
|
||||
}
|
||||
}
|
||||
StringBuilder colorDesc = new StringBuilder();
|
||||
for (final String col : substitutedColors) {
|
||||
if (col.equalsIgnoreCase("White")) {
|
||||
colorDesc.append("W ");
|
||||
} else if (col.equalsIgnoreCase("Blue")) {
|
||||
colorDesc.append("U ");
|
||||
} else if (col.equalsIgnoreCase("Black")) {
|
||||
colorDesc.append("B ");
|
||||
} else if (col.equalsIgnoreCase("Red")) {
|
||||
colorDesc.append("R ");
|
||||
} else if (col.equalsIgnoreCase("Green")) {
|
||||
colorDesc.append("G ");
|
||||
} else if (col.equalsIgnoreCase("Colorless")) {
|
||||
colorDesc = new StringBuilder("C");
|
||||
}
|
||||
}
|
||||
result.setOwner(ai);
|
||||
|
||||
final List<String> imageNames = new ArrayList<>(1);
|
||||
if (tokenImage.equals("")) {
|
||||
imageNames.add(PaperToken.makeTokenFileName(TextUtil.fastReplace(colorDesc.toString(), " ", ""), tokenPower, tokenToughness, tokenName));
|
||||
} else {
|
||||
imageNames.add(0, tokenImage);
|
||||
}
|
||||
|
||||
for (final char c : colorDesc.toString().toCharArray()) {
|
||||
cost.append(c + ' ');
|
||||
}
|
||||
|
||||
cost = new StringBuilder(colorDesc.toString().replace('C', '1').trim());
|
||||
|
||||
final int finalPower = AbilityUtils.calculateAmount(host, tokenPower, sa);
|
||||
final int finalToughness = AbilityUtils.calculateAmount(host, tokenToughness, sa);
|
||||
|
||||
final String[] substitutedTypes = Arrays.copyOf(tokenTypes, tokenTypes.length);
|
||||
for (int i = 0; i < substitutedTypes.length; i++) {
|
||||
if (substitutedTypes[i].equals("ChosenType")) {
|
||||
substitutedTypes[i] = host.getChosenType();
|
||||
}
|
||||
}
|
||||
final String substitutedName = tokenName.equals("ChosenType") ? host.getChosenType() : tokenName;
|
||||
final String imageName = imageNames.get(MyRandom.getRandom().nextInt(imageNames.size()));
|
||||
final TokenInfo tokenInfo = new TokenInfo(substitutedName, imageName,
|
||||
cost.toString(), substitutedTypes, tokenKeywords, finalPower, finalToughness);
|
||||
Card token = tokenInfo.makeOneToken(ai);
|
||||
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Grant rule changes
|
||||
if (tokenHiddenKeywords != null) {
|
||||
for (final String s : tokenHiddenKeywords) {
|
||||
token.addHiddenExtrinsicKeyword(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Grant abilities
|
||||
if (tokenAbilities != null) {
|
||||
for (final String s : tokenAbilities) {
|
||||
final String actualAbility = host.getSVar(s);
|
||||
final SpellAbility grantedAbility = AbilityFactory.getAbility(actualAbility, token);
|
||||
token.addSpellAbility(grantedAbility);
|
||||
}
|
||||
}
|
||||
|
||||
// Grant triggers
|
||||
if (tokenTriggers != null) {
|
||||
for (final String s : tokenTriggers) {
|
||||
final String actualTrigger = host.getSVar(s);
|
||||
final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, token, true);
|
||||
final String ability = host.getSVar(parsedTrigger.getParam("Execute"));
|
||||
parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(ability, token));
|
||||
token.addTrigger(parsedTrigger);
|
||||
}
|
||||
}
|
||||
|
||||
// Grant SVars
|
||||
if (tokenSVars != null) {
|
||||
for (final String s : tokenSVars) {
|
||||
String actualSVar = host.getSVar(s);
|
||||
String name = s;
|
||||
if (actualSVar.startsWith("SVar")) {
|
||||
actualSVar = actualSVar.split("SVar:")[1];
|
||||
name = actualSVar.split(":")[0];
|
||||
actualSVar = actualSVar.split(":")[1];
|
||||
}
|
||||
token.setSVar(name, actualSVar);
|
||||
}
|
||||
}
|
||||
|
||||
// Grant static abilities
|
||||
if (tokenStaticAbilities != null) {
|
||||
for (final String s : tokenStaticAbilities) {
|
||||
token.addStaticAbility(host.getSVar(s));
|
||||
}
|
||||
}
|
||||
|
||||
// Apply static abilities and prune dead tokens
|
||||
// Apply static abilities
|
||||
final Game game = ai.getGame();
|
||||
ComputerUtilCard.applyStaticContPT(game, token, null);
|
||||
if (!notNull && token.isCreature() && token.getNetToughness() < 1) {
|
||||
return null;
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
ComputerUtilCard.applyStaticContPT(game, result, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import forge.game.spellability.TargetRestrictions;
|
||||
import forge.game.zone.ZoneType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class UntapAi extends SpellAbilityAi {
|
||||
@Override
|
||||
@@ -153,12 +154,11 @@ public class UntapAi extends SpellAbilityAi {
|
||||
|
||||
// Try to avoid potential infinite recursion,
|
||||
// e.g. Kiora's Follower untapping another Kiora's Follower and repeating infinitely
|
||||
if (sa.getPayCosts() != null && sa.getPayCosts().hasOnlySpecificCostType(CostTap.class)) {
|
||||
if (sa.getPayCosts().hasOnlySpecificCostType(CostTap.class)) {
|
||||
CardCollection toRemove = new CardCollection();
|
||||
for (Card c : untapList) {
|
||||
for (SpellAbility ab : c.getAllSpellAbilities()) {
|
||||
if (ab.getApi() == ApiType.Untap
|
||||
&& ab.getPayCosts() != null
|
||||
&& ab.getPayCosts().hasOnlySpecificCostType(CostTap.class)
|
||||
&& ab.canTarget(source)) {
|
||||
toRemove.add(c);
|
||||
@@ -312,7 +312,7 @@ public class UntapAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> list, boolean isOptional, Player targetedPlayer) {
|
||||
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> list, boolean isOptional, Player targetedPlayer, Map<String, Object> params) {
|
||||
PlayerCollection pl = new PlayerCollection();
|
||||
pl.add(ai);
|
||||
pl.addAll(ai.getAllies());
|
||||
|
||||
@@ -76,7 +76,7 @@ public class GameCopier {
|
||||
newPlayer.addSpellCastThisTurn();
|
||||
for (int j = 0; j < origPlayer.getLandsPlayedThisTurn(); j++)
|
||||
newPlayer.addLandPlayedThisTurn();
|
||||
newPlayer.setCounters(Maps.newEnumMap(origPlayer.getCounters()));
|
||||
newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters()));
|
||||
newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn());
|
||||
newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn());
|
||||
newPlayer.setPreventNextDamage(origPlayer.getPreventNextDamage());
|
||||
@@ -125,8 +125,6 @@ public class GameCopier {
|
||||
}
|
||||
}
|
||||
}
|
||||
origGame.validateSpabCache();
|
||||
newGame.validateSpabCache();
|
||||
|
||||
// Undo effects first before calculating them below, to avoid them applying twice.
|
||||
for (StaticEffect effect : origGame.getStaticEffects().getEffects()) {
|
||||
@@ -330,7 +328,7 @@ public class GameCopier {
|
||||
|
||||
Map<CounterType, Integer> counters = c.getCounters();
|
||||
if (!counters.isEmpty()) {
|
||||
newCard.setCounters(Maps.newEnumMap(counters));
|
||||
newCard.setCounters(Maps.newHashMap(counters));
|
||||
}
|
||||
if (c.getChosenPlayer() != null) {
|
||||
newCard.setChosenPlayer(playerMap.get(c.getChosenPlayer()));
|
||||
|
||||
@@ -3,7 +3,7 @@ package forge.ai.simulation;
|
||||
import forge.ai.CreatureEvaluator;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.card.CounterEnumType;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
@@ -154,7 +154,7 @@ public class GameStateEvaluator {
|
||||
// e.g. a 5 CMC permanent results in 200, whereas a 5/5 creature is ~225
|
||||
int value = 50 + 30 * c.getCMC();
|
||||
if (c.isPlaneswalker()) {
|
||||
value += 2 * c.getCounters(CounterType.LOYALTY);
|
||||
value += 2 * c.getCounters(CounterEnumType.LOYALTY);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.34-SNAPSHOT</version>
|
||||
<version>1.6.37-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-core</artifactId>
|
||||
|
||||
@@ -6,6 +6,8 @@ import forge.util.ImageUtil;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -116,9 +118,15 @@ public final class ImageKeys {
|
||||
String fullborderFile = TextUtil.fastReplace(filename, ".full", ".fullborder");
|
||||
file = findFile(dir, fullborderFile);
|
||||
if (file != null) { return file; }
|
||||
// if there's an art variant try without it
|
||||
// if there's a 1st art variant try without it for .fullborder images
|
||||
file = findFile(dir, TextUtil.fastReplace(fullborderFile, "1.fullborder", ".fullborder"));
|
||||
if (file != null) { return file; }
|
||||
// if there's an art variant try without it for .full images
|
||||
file = findFile(dir, filename.replaceAll("[0-9].full",".full"));
|
||||
if (file != null) { return file; }
|
||||
// if there's a 1st art variant try with it for .full images
|
||||
file = findFile(dir, filename.replaceAll("[0-9]*.full", "1.full"));
|
||||
if (file != null) { return file; }
|
||||
}
|
||||
//if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder
|
||||
if (!filename.contains(".full")) {
|
||||
|
||||
@@ -9,15 +9,17 @@ public class CardAiHints {
|
||||
|
||||
private final boolean isRemovedFromAIDecks;
|
||||
private final boolean isRemovedFromRandomDecks;
|
||||
private final boolean isRemovedFromNonCommanderDecks;
|
||||
|
||||
private final DeckHints deckHints;
|
||||
private final DeckHints deckNeeds;
|
||||
private final DeckHints deckHas;
|
||||
|
||||
|
||||
public CardAiHints(boolean remAi, boolean remRandom, DeckHints dh, DeckHints dn, DeckHints has) {
|
||||
public CardAiHints(boolean remAi, boolean remRandom, boolean remUnlessCommander, DeckHints dh, DeckHints dn, DeckHints has) {
|
||||
isRemovedFromAIDecks = remAi;
|
||||
isRemovedFromRandomDecks = remRandom;
|
||||
isRemovedFromNonCommanderDecks = remUnlessCommander;
|
||||
deckHints = dh;
|
||||
deckNeeds = dn;
|
||||
deckHas = has;
|
||||
@@ -42,8 +44,17 @@ public class CardAiHints {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the deckHints
|
||||
* Gets the rem random decks.
|
||||
*
|
||||
* @return the rem random decks
|
||||
*/
|
||||
public boolean getRemNonCommanderDecks() {
|
||||
return this.isRemovedFromNonCommanderDecks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the deckHints
|
||||
*/
|
||||
public DeckHints getDeckHints() {
|
||||
return deckHints;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
private String additionalUnlockSet = "";
|
||||
private boolean smallSetOverride = false;
|
||||
private String boosterMustContain = "";
|
||||
private boolean doublePickToStartRound = false;
|
||||
private final CardInSet[] cards;
|
||||
private final Map<String, Integer> tokenNormalized;
|
||||
|
||||
@@ -190,6 +191,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
public String getAdditionalSheetForFoils() { return additionalSheetForFoils; }
|
||||
public String getAdditionalUnlockSet() { return additionalUnlockSet; }
|
||||
public boolean getSmallSetOverride() { return smallSetOverride; }
|
||||
public boolean getDoublePickToStartRound() { return doublePickToStartRound; }
|
||||
public String getBoosterMustContain() { return boosterMustContain; }
|
||||
public CardInSet[] getCards() { return cards; }
|
||||
public boolean isModern() { return getDate().after(parseDate("2003-07-27")); } //8ED and above are modern except some promo cards and others
|
||||
@@ -377,6 +379,7 @@ public final class CardEdition implements Comparable<CardEdition> { // immutable
|
||||
res.additionalUnlockSet = section.get("AdditionalSetUnlockedInQuest", ""); // e.g. Time Spiral Timeshifted (TSB) for Time Spiral
|
||||
|
||||
res.smallSetOverride = section.getBoolean("TreatAsSmallSet", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
|
||||
res.doublePickToStartRound = section.getBoolean("DoublePick", false); // for "small" sets with over 200 cards (e.g. Eldritch Moon)
|
||||
|
||||
res.boosterMustContain = section.get("BoosterMustContain", ""); // e.g. Dominaria guaranteed legendary creature
|
||||
return res;
|
||||
|
||||
@@ -291,6 +291,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
// fields to build CardAiHints
|
||||
private boolean removedFromAIDecks = false;
|
||||
private boolean removedFromRandomDecks = false;
|
||||
private boolean removedFromNonCommanderDecks = false;
|
||||
private DeckHints hints = null;
|
||||
private DeckHints needs = null;
|
||||
private DeckHints has = null;
|
||||
@@ -310,6 +311,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
|
||||
this.removedFromAIDecks = false;
|
||||
this.removedFromRandomDecks = false;
|
||||
this.removedFromNonCommanderDecks = false;
|
||||
this.needs = null;
|
||||
this.hints = null;
|
||||
this.has = null;
|
||||
@@ -324,7 +326,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
* @return the card
|
||||
*/
|
||||
public final CardRules getCard() {
|
||||
CardAiHints cah = new CardAiHints(removedFromAIDecks, removedFromRandomDecks, hints, needs, has);
|
||||
CardAiHints cah = new CardAiHints(removedFromAIDecks, removedFromRandomDecks, removedFromNonCommanderDecks, hints, needs, has);
|
||||
faces[0].assignMissingFields();
|
||||
if (null != faces[1]) faces[1].assignMissingFields();
|
||||
final CardRules result = new CardRules(faces, altMode, cah);
|
||||
@@ -377,6 +379,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
if ( "RemoveDeck".equals(variable) ) {
|
||||
this.removedFromAIDecks = "All".equalsIgnoreCase(value);
|
||||
this.removedFromRandomDecks = "Random".equalsIgnoreCase(value);
|
||||
this.removedFromNonCommanderDecks = "NonCommander".equalsIgnoreCase(value);
|
||||
}
|
||||
} else if ("AlternateMode".equals(key)) {
|
||||
//System.out.println(faces[curFace].getName());
|
||||
@@ -531,12 +534,10 @@ public final class CardRules implements ICardCharacteristics {
|
||||
public final ManaCostShard next() {
|
||||
final String unparsed = st.nextToken();
|
||||
// System.out.println(unparsed);
|
||||
try {
|
||||
int iVal = Integer.parseInt(unparsed);
|
||||
this.genericCost += iVal;
|
||||
if (StringUtils.isNumeric(unparsed)) {
|
||||
this.genericCost += Integer.parseInt(unparsed);
|
||||
return null;
|
||||
}
|
||||
catch (NumberFormatException nex) { }
|
||||
|
||||
return ManaCostShard.parseNonGeneric(unparsed);
|
||||
}
|
||||
@@ -553,7 +554,7 @@ public final class CardRules implements ICardCharacteristics {
|
||||
}
|
||||
|
||||
public static CardRules getUnsupportedCardNamed(String name) {
|
||||
CardAiHints cah = new CardAiHints(true, true, null, null, null);
|
||||
CardAiHints cah = new CardAiHints(true, true, true, null, null, null);
|
||||
CardFace[] faces = { new CardFace(name), null};
|
||||
faces[0].setColor(ColorSet.fromMask(0));
|
||||
faces[0].setType(CardType.parse(""));
|
||||
|
||||
@@ -9,7 +9,8 @@ public enum CardSplitType
|
||||
Meld(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Meld),
|
||||
Split(FaceSelectionMethod.COMBINE, CardStateName.RightSplit),
|
||||
Flip(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Flipped),
|
||||
Adventure(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Adventure);
|
||||
Adventure(FaceSelectionMethod.USE_PRIMARY_FACE, CardStateName.Adventure),
|
||||
Modal(FaceSelectionMethod.USE_ACTIVE_FACE, CardStateName.Modal);
|
||||
|
||||
CardSplitType(FaceSelectionMethod calcMode, CardStateName stateName) {
|
||||
method = calcMode;
|
||||
|
||||
@@ -10,6 +10,7 @@ public enum CardStateName {
|
||||
LeftSplit,
|
||||
RightSplit,
|
||||
Adventure,
|
||||
Modal
|
||||
|
||||
;
|
||||
|
||||
|
||||
@@ -72,6 +72,14 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
private static Map<String, CoreType> stringToCoreType = EnumUtils.getEnumMap(CoreType.class);
|
||||
private static final Set<String> allCoreTypeNames = stringToCoreType.keySet();
|
||||
|
||||
public static CoreType getEnum(String name) {
|
||||
return stringToCoreType.get(name);
|
||||
}
|
||||
|
||||
public static boolean isValidEnum(String name) {
|
||||
return stringToCoreType.containsKey(name);
|
||||
}
|
||||
|
||||
CoreType(final boolean permanent) {
|
||||
isPermanent = permanent;
|
||||
}
|
||||
@@ -87,6 +95,15 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
|
||||
private static Map<String, Supertype> stringToSupertype = EnumUtils.getEnumMap(Supertype.class);
|
||||
private static final Set<String> allSuperTypeNames = stringToSupertype.keySet();
|
||||
|
||||
public static Supertype getEnum(String name) {
|
||||
return stringToSupertype.get(name);
|
||||
}
|
||||
|
||||
public static boolean isValidEnum(String name) {
|
||||
return stringToSupertype.containsKey(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Set<CoreType> coreTypes = EnumSet.noneOf(CoreType.class);
|
||||
@@ -108,12 +125,12 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
|
||||
public boolean add(final String t) {
|
||||
boolean changed;
|
||||
final CoreType ct = EnumUtils.getEnum(CoreType.class, t);
|
||||
final CoreType ct = CoreType.getEnum(t);
|
||||
if (ct != null) {
|
||||
changed = coreTypes.add(ct);
|
||||
}
|
||||
else {
|
||||
final Supertype st = EnumUtils.getEnum(Supertype.class, t);
|
||||
final Supertype st = Supertype.getEnum(t);
|
||||
if (st != null) {
|
||||
changed = supertypes.add(st);
|
||||
}
|
||||
@@ -183,11 +200,11 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
if (subtypes.remove(str)) {
|
||||
changed = true;
|
||||
} else {
|
||||
Supertype st = EnumUtils.getEnum(Supertype.class, str);
|
||||
Supertype st = Supertype.getEnum(str);
|
||||
if (st != null && supertypes.remove(st)) {
|
||||
changed = true;
|
||||
}
|
||||
CoreType ct = EnumUtils.getEnum(CoreType.class, str);
|
||||
CoreType ct = CoreType.getEnum(str);
|
||||
if (ct != null && coreTypes.remove(ct)) {
|
||||
changed = true;
|
||||
}
|
||||
@@ -265,11 +282,11 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
}
|
||||
|
||||
t = StringUtils.capitalize(t);
|
||||
final CoreType type = EnumUtils.getEnum(CoreType.class, t);
|
||||
final CoreType type = CoreType.getEnum(t);
|
||||
if (type != null) {
|
||||
return hasType(type);
|
||||
}
|
||||
final Supertype supertype = EnumUtils.getEnum(Supertype.class, t);
|
||||
final Supertype supertype = Supertype.getEnum(t);
|
||||
if (supertype != null) {
|
||||
return hasSupertype(supertype);
|
||||
}
|
||||
@@ -660,7 +677,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
|
||||
///////// Utility methods
|
||||
public static boolean isACardType(final String cardType) {
|
||||
return EnumUtils.isValidEnum(CoreType.class, cardType);
|
||||
return CoreType.isValidEnum(cardType);
|
||||
}
|
||||
|
||||
public static Set<String> getAllCardTypes() {
|
||||
@@ -708,7 +725,7 @@ public final class CardType implements Comparable<CardType>, CardTypeView {
|
||||
}
|
||||
|
||||
public static boolean isASupertype(final String cardType) {
|
||||
return EnumUtils.isValidEnum(Supertype.class, cardType);
|
||||
return Supertype.isValidEnum(cardType);
|
||||
}
|
||||
|
||||
public static boolean isASubType(final String cardType) {
|
||||
|
||||
@@ -78,6 +78,16 @@ public class PrintSheet {
|
||||
return fetchRoulette(sum + 1, roulette, toSkip); // start over from beginning, in case last cards were to skip
|
||||
}
|
||||
|
||||
public List<PaperCard> all() {
|
||||
List<PaperCard> result = new ArrayList<>();
|
||||
for(Entry<PaperCard, Integer> kv : cardsWithWeights) {
|
||||
for(int i = 0; i < kv.getValue(); i++) {
|
||||
result.add(kv.getKey());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<PaperCard> random(int number, boolean wantUnique) {
|
||||
List<PaperCard> result = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -289,13 +289,20 @@ public enum ManaCostShard {
|
||||
return BinaryUtil.bitCount(this.shard & COLORS_SUPERPOSITION) == 2;
|
||||
}
|
||||
|
||||
public boolean isGeneric() {
|
||||
return isOfKind(ManaAtom.GENERIC)|| isOfKind(ManaAtom.IS_X) || this.isSnow() || this.isOr2Generic();
|
||||
}
|
||||
public boolean isOr2Generic() {
|
||||
return isOfKind(ManaAtom.OR_2_GENERIC);
|
||||
}
|
||||
|
||||
public boolean isColor(byte colorCode) {
|
||||
return (colorCode & this.shard) > 0;
|
||||
}
|
||||
|
||||
public boolean canBePaidWithManaOfColor(byte colorCode) {
|
||||
return this.isOr2Generic() || ((COLORS_SUPERPOSITION | ManaAtom.COLORLESS) & this.shard) == 0 ||
|
||||
(colorCode & this.shard) > 0;
|
||||
this.isColor(colorCode);
|
||||
}
|
||||
|
||||
public boolean isOfKind(int atom) {
|
||||
|
||||
@@ -290,10 +290,18 @@ public abstract class DeckGeneratorBase {
|
||||
// remove cards that generated decks don't like
|
||||
Predicate<CardRules> canPlay = forAi ? AI_CAN_PLAY : HUMAN_CAN_PLAY;
|
||||
Predicate<CardRules> hasColor = new MatchColorIdentity(colors);
|
||||
Predicate<CardRules> canUseInFormat = new Predicate<CardRules>() {
|
||||
@Override
|
||||
public boolean apply(CardRules c) {
|
||||
// FIXME: should this be limited to AI only (!forAi) or should it be generally applied to all random generated decks?
|
||||
return !c.getAiHints().getRemNonCommanderDecks() || format.hasCommander();
|
||||
}
|
||||
};
|
||||
|
||||
if (useArtifacts) {
|
||||
hasColor = Predicates.or(hasColor, COLORLESS_CARDS);
|
||||
}
|
||||
return Iterables.filter(pool.getAllCards(),Predicates.compose(Predicates.and(canPlay, hasColor), PaperCard.FN_GET_RULES));
|
||||
return Iterables.filter(pool.getAllCards(),Predicates.compose(Predicates.and(canPlay, hasColor, canUseInFormat), PaperCard.FN_GET_RULES));
|
||||
}
|
||||
|
||||
protected static Map<String, Integer> countLands(ItemPool<PaperCard> outList) {
|
||||
|
||||
@@ -229,6 +229,13 @@ public class BoosterGenerator {
|
||||
String sheetKey = StaticData.instance().getEditions().contains(setCode) ? slotType.trim() + " " + setCode
|
||||
: slotType.trim();
|
||||
|
||||
if (sheetKey.startsWith("wholeSheet")) {
|
||||
PrintSheet ps = getPrintSheet(sheetKey);
|
||||
result.addAll(ps.all());
|
||||
sheetsUsed.add(ps);
|
||||
continue;
|
||||
}
|
||||
|
||||
slotType = slotType.split("[ :!]")[0]; // add expansion symbol here?
|
||||
|
||||
boolean foilInThisSlot = hasFoil && (slotType.equals(foilSlot));
|
||||
@@ -438,9 +445,10 @@ public class BoosterGenerator {
|
||||
|
||||
String mainCode = itMod.next();
|
||||
|
||||
if (mainCode.regionMatches(true, 0, "fromSheet", 0, 9)) { // custom print sheet
|
||||
|
||||
String sheetName = StringUtils.strip(mainCode.substring(9), "()\" ");
|
||||
if (mainCode.regionMatches(true, 0, "fromSheet", 0, 9) ||
|
||||
mainCode.regionMatches(true, 0, "wholeSheet", 0, 10)
|
||||
) { // custom print sheet
|
||||
String sheetName = StringUtils.strip(mainCode.substring(10), "()\" ");
|
||||
src = StaticData.instance().getPrintSheets().get(sheetName).toFlatList();
|
||||
setPred = Predicates.alwaysTrue();
|
||||
|
||||
@@ -524,7 +532,12 @@ public class BoosterGenerator {
|
||||
|
||||
Predicate<PaperCard> toAdd = null;
|
||||
if (operator.equalsIgnoreCase(BoosterSlots.DUAL_FACED_CARD)) {
|
||||
toAdd = Predicates.compose(Predicates.or(CardRulesPredicates.splitType(CardSplitType.Transform), CardRulesPredicates.splitType(CardSplitType.Meld)),
|
||||
toAdd = Predicates.compose(
|
||||
Predicates.or(
|
||||
CardRulesPredicates.splitType(CardSplitType.Transform),
|
||||
CardRulesPredicates.splitType(CardSplitType.Meld),
|
||||
CardRulesPredicates.splitType(CardSplitType.Modal)
|
||||
),
|
||||
PaperCard.FN_GET_RULES);
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.LAND)) { toAdd = Predicates.compose(CardRulesPredicates.Presets.IS_LAND, PaperCard.FN_GET_RULES);
|
||||
} else if (operator.equalsIgnoreCase(BoosterSlots.BASIC_LAND)) { toAdd = IPaperCard.Predicates.Presets.IS_BASIC_LAND;
|
||||
|
||||
@@ -81,7 +81,7 @@ public class ImageUtil {
|
||||
|
||||
public static boolean hasBackFacePicture(PaperCard cp) {
|
||||
CardSplitType cst = cp.getRules().getSplitType();
|
||||
return cst == CardSplitType.Transform || cst == CardSplitType.Flip || cst == CardSplitType.Meld;
|
||||
return cst == CardSplitType.Transform || cst == CardSplitType.Flip || cst == CardSplitType.Meld || cst == CardSplitType.Modal;
|
||||
}
|
||||
|
||||
public static String getNameToUse(PaperCard cp, boolean backFace) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import forge.item.InventoryItem;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
@@ -60,7 +61,7 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
|
||||
};
|
||||
|
||||
public ItemPool(final Class<T> cls) {
|
||||
this(new LinkedHashMap<>(), cls);
|
||||
this(new ConcurrentHashMap<>(), cls);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -95,7 +96,7 @@ public class ItemPool<T extends InventoryItem> implements Iterable<Entry<T, Inte
|
||||
items = items0;
|
||||
}
|
||||
else {
|
||||
items = new HashMap<>(); //prevent items being null
|
||||
items = new ConcurrentHashMap<>();
|
||||
}
|
||||
myClass = cls;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>forge</artifactId>
|
||||
<groupId>forge</groupId>
|
||||
<version>1.6.34-SNAPSHOT</version>
|
||||
<version>1.6.37-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>forge-game</artifactId>
|
||||
|
||||
@@ -5,7 +5,6 @@ import forge.card.mana.ManaAtom;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CardView;
|
||||
@@ -243,7 +242,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
||||
final String type = params.get("Presence");
|
||||
|
||||
int revealed = AbilityUtils.calculateAmount(hostCard, "Revealed$Valid " + type, hostCard.getCastSA());
|
||||
int ctrl = AbilityUtils.calculateAmount(hostCard, "Count$Valid " + type + ".inZoneBattlefield+YouCtrl", hostCard.getCastSA());
|
||||
int ctrl = AbilityUtils.calculateAmount(hostCard, "Count$LastStateBattlefield " + type + ".YouCtrl", hostCard.getCastSA());
|
||||
|
||||
if (revealed + ctrl == 0) {
|
||||
return false;
|
||||
@@ -271,13 +270,8 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
||||
lifeCompare = params.get("LifeAmount");
|
||||
}
|
||||
|
||||
int right = 1;
|
||||
final String rightString = lifeCompare.substring(2);
|
||||
try {
|
||||
right = Integer.parseInt(rightString);
|
||||
} catch (final NumberFormatException nfe) {
|
||||
right = CardFactoryUtil.xCount(this.getHostCard(), this.getHostCard().getSVar(rightString));
|
||||
}
|
||||
int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
|
||||
|
||||
if (!Expressions.compare(life, lifeCompare, right)) {
|
||||
return false;
|
||||
@@ -314,13 +308,9 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
||||
}
|
||||
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), null);
|
||||
|
||||
int right = 1;
|
||||
|
||||
final String rightString = presentCompare.substring(2);
|
||||
try {
|
||||
right = Integer.parseInt(rightString);
|
||||
} catch (final NumberFormatException nfe) {
|
||||
right = CardFactoryUtil.xCount(this.getHostCard(), this.getHostCard().getSVar(rightString));
|
||||
}
|
||||
int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
|
||||
final int left = list.size();
|
||||
|
||||
if (!Expressions.compare(left, presentCompare, right)) {
|
||||
@@ -355,13 +345,8 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
||||
|
||||
list = CardLists.getValidCards(list, sIsPresent.split(","), this.getHostCard().getController(), this.getHostCard(), null);
|
||||
|
||||
int right = 1;
|
||||
final String rightString = presentCompare.substring(2);
|
||||
try {
|
||||
right = Integer.parseInt(rightString);
|
||||
} catch (final NumberFormatException nfe) {
|
||||
right = CardFactoryUtil.xCount(this.getHostCard(), this.getHostCard().getSVar(rightString));
|
||||
}
|
||||
int right = AbilityUtils.calculateAmount(getHostCard(), rightString, this);
|
||||
final int left = list.size();
|
||||
|
||||
if (!Expressions.compare(left, presentCompare, right)) {
|
||||
|
||||
6
forge-game/src/main/java/forge/game/EvenOdd.java
Normal file
6
forge-game/src/main/java/forge/game/EvenOdd.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package forge.game;
|
||||
|
||||
public enum EvenOdd {
|
||||
Even,
|
||||
Odd
|
||||
}
|
||||
@@ -81,7 +81,7 @@ public class ForgeScript {
|
||||
return !cardState.getTypeWithChanges().hasSubtype(subType);
|
||||
} else if (property.equals("hasActivatedAbilityWithTapCost")) {
|
||||
for (final SpellAbility sa : cardState.getSpellAbilities()) {
|
||||
if (sa.isAbility() && (sa.getPayCosts() != null) && sa.getPayCosts().hasTapCost()) {
|
||||
if (sa.isAbility() && sa.getPayCosts().hasTapCost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -108,14 +108,9 @@ public class ForgeScript {
|
||||
}
|
||||
return false;
|
||||
} else if (property.startsWith("cmc")) {
|
||||
int x;
|
||||
String rhs = property.substring(5);
|
||||
int y = cardState.getManaCost().getCMC();
|
||||
try {
|
||||
x = Integer.parseInt(rhs);
|
||||
} catch (final NumberFormatException e) {
|
||||
x = AbilityUtils.calculateAmount(source, rhs, spellAbility);
|
||||
}
|
||||
int x = AbilityUtils.calculateAmount(source, rhs, spellAbility);
|
||||
|
||||
return Expressions.compare(y, property, x);
|
||||
} else return cardState.getTypeWithChanges().hasStringType(property);
|
||||
@@ -130,6 +125,8 @@ public class ForgeScript {
|
||||
return sa.isManaAbility();
|
||||
} else if (property.equals("nonManaAbility")) {
|
||||
return !sa.isManaAbility();
|
||||
} else if (property.equals("withoutXCost")) {
|
||||
return !sa.isXCost();
|
||||
} else if (property.equals("Buyback")) {
|
||||
return sa.isBuyBackAbility();
|
||||
} else if (property.equals("Cycling")) {
|
||||
|
||||
@@ -40,7 +40,6 @@ import forge.game.player.*;
|
||||
import forge.game.replacement.ReplacementHandler;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityStackInstance;
|
||||
import forge.game.spellability.SpellAbilityView;
|
||||
import forge.game.trigger.TriggerHandler;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.CostPaymentStack;
|
||||
@@ -178,19 +177,6 @@ public class Game {
|
||||
playerCache.put(Integer.valueOf(id), player);
|
||||
}
|
||||
|
||||
private final GameEntityCache<Card, CardView> cardCache = new GameEntityCache<>();
|
||||
public Card getCard(CardView cardView) {
|
||||
return cardCache.get(cardView);
|
||||
}
|
||||
public void addCard(int id, Card card) {
|
||||
cardCache.put(Integer.valueOf(id), card);
|
||||
}
|
||||
public CardCollection getCardList(Iterable<CardView> cardViews) {
|
||||
CardCollection list = new CardCollection();
|
||||
cardCache.addToList(cardViews, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
// methods that deal with saving, retrieving and clearing LKI information about cards on zone change
|
||||
private final HashMap<Integer, Card> changeZoneLKIInfo = new HashMap<>();
|
||||
public final void addChangeZoneLKIInfo(Card c) {
|
||||
@@ -209,27 +195,6 @@ public class Game {
|
||||
changeZoneLKIInfo.clear();
|
||||
}
|
||||
|
||||
private final GameEntityCache<SpellAbility, SpellAbilityView> spabCache = new GameEntityCache<>();
|
||||
public SpellAbility getSpellAbility(final SpellAbilityView view) {
|
||||
return spabCache.get(view);
|
||||
}
|
||||
public void addSpellAbility(SpellAbility spellAbility) {
|
||||
spabCache.put(spellAbility.getId(), spellAbility);
|
||||
}
|
||||
public void removeSpellAbility(SpellAbility spellAbility) {
|
||||
spabCache.remove(spellAbility.getId());
|
||||
}
|
||||
public void validateSpabCache() {
|
||||
for (SpellAbility sa : spabCache.getValues()) {
|
||||
if (sa.getHostCard() != null && sa.getHostCard().getGame() != this) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if (sa.getActivatingPlayer() != null && sa.getActivatingPlayer().getGame() != this) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Game(List<RegisteredPlayer> players0, GameRules rules0, Match match0) { /* no more zones to map here */
|
||||
rules = rules0;
|
||||
match = match0;
|
||||
@@ -559,6 +524,48 @@ public class Game {
|
||||
return visit.getFound(notFound);
|
||||
}
|
||||
|
||||
private static class CardIdVisitor extends Visitor<Card> {
|
||||
Card found = null;
|
||||
int id;
|
||||
|
||||
private CardIdVisitor(final int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(Card object) {
|
||||
if (this.id == object.getId()) {
|
||||
found = object;
|
||||
}
|
||||
return found == null;
|
||||
}
|
||||
|
||||
public Card getFound() {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
public Card findByView(CardView view) {
|
||||
if (view == null) {
|
||||
return null;
|
||||
}
|
||||
CardIdVisitor visit = new CardIdVisitor(view.getId());
|
||||
if (ZoneType.Stack.equals(view.getZone())) {
|
||||
visit.visitAll(getStackZone());
|
||||
} else if (view.getController() != null && view.getZone() != null) {
|
||||
visit.visitAll(getPlayer(view.getController()).getZone(view.getZone()));
|
||||
} else { // fallback if view doesn't has controller or zone set for some reason
|
||||
forEachCardInGame(visit);
|
||||
}
|
||||
return visit.getFound();
|
||||
}
|
||||
|
||||
public Card findById(int id) {
|
||||
CardIdVisitor visit = new CardIdVisitor(id);
|
||||
this.forEachCardInGame(visit);
|
||||
return visit.getFound();
|
||||
}
|
||||
|
||||
// Allows visiting cards in game without allocating a temporary list.
|
||||
public void forEachCardInGame(Visitor<Card> visitor) {
|
||||
for (final Player player : getPlayers()) {
|
||||
@@ -672,21 +679,25 @@ public class Game {
|
||||
// Rule 800.4 Losing a Multiplayer game
|
||||
CardCollectionView cards = this.getCardsInGame();
|
||||
boolean planarControllerLost = false;
|
||||
boolean isMultiplayer = this.getPlayers().size() > 2;
|
||||
|
||||
for(Card c : cards) {
|
||||
if (c.getController().equals(p) && (c.isPlane() || c.isPhenomenon())) {
|
||||
planarControllerLost = true;
|
||||
}
|
||||
|
||||
if (c.getOwner().equals(p)) {
|
||||
c.ceaseToExist();
|
||||
} else {
|
||||
c.removeTempController(p);
|
||||
if (c.getController().equals(p)) {
|
||||
this.getAction().exile(c, null);
|
||||
if(isMultiplayer) {
|
||||
if (c.getOwner().equals(p)) {
|
||||
c.ceaseToExist();
|
||||
} else {
|
||||
c.removeTempController(p);
|
||||
if (c.getController().equals(p)) {
|
||||
this.getAction().exile(c, null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.forceTurnFaceUp();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 901.6: If the current planar controller would leave the game, instead the next player
|
||||
@@ -892,8 +903,9 @@ public class Game {
|
||||
}
|
||||
|
||||
public void clearCaches() {
|
||||
spabCache.clear();
|
||||
cardCache.clear();
|
||||
|
||||
lastStateBattlefield.clear();
|
||||
lastStateGraveyard.clear();
|
||||
//playerCache.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import forge.game.player.Player;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementResult;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.AbilitySub;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.spellability.SpellAbilityPredicates;
|
||||
import forge.game.staticability.StaticAbility;
|
||||
@@ -104,7 +103,7 @@ public class GameAction {
|
||||
boolean wasFacedown = c.isFaceDown();
|
||||
|
||||
//Rule 110.5g: A token that has left the battlefield can't move to another zone
|
||||
if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Command)) {
|
||||
if (c.isToken() && zoneFrom != null && !fromBattlefield) {
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -157,11 +156,6 @@ public class GameAction {
|
||||
c.removeSVar("EndOfTurnLeavePlay");
|
||||
}
|
||||
|
||||
// Clean up temporary variables such as Sunburst value or announced PayX value
|
||||
if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) {
|
||||
c.clearTemporaryVars();
|
||||
}
|
||||
|
||||
if (fromBattlefield && !toBattlefield) {
|
||||
c.getController().setRevolt(true);
|
||||
}
|
||||
@@ -173,7 +167,7 @@ public class GameAction {
|
||||
|
||||
// if to Battlefield and it is caused by an replacement effect,
|
||||
// try to get previous LKI if able
|
||||
if (zoneTo.is(ZoneType.Battlefield)) {
|
||||
if (toBattlefield) {
|
||||
if (cause != null && cause.isReplacementAbility()) {
|
||||
ReplacementEffect re = cause.getReplacementEffect();
|
||||
if (ReplacementType.Moved.equals(re.getMode())) {
|
||||
@@ -185,6 +179,10 @@ public class GameAction {
|
||||
if (lastKnownInfo == null) {
|
||||
lastKnownInfo = CardUtil.getLKICopy(c);
|
||||
}
|
||||
|
||||
if (!lastKnownInfo.hasKeyword("Counters remain on CARDNAME as it moves to any zone other than a player's hand or library.") || zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library)) {
|
||||
copied.clearCounters();
|
||||
}
|
||||
} else {
|
||||
// if from Battlefield to Graveyard and Card does exist in LastStateBattlefield
|
||||
// use that instead
|
||||
@@ -204,7 +202,7 @@ public class GameAction {
|
||||
// all sort of funky shenanigans may happen later (e.g. their ETB replacement effects are set
|
||||
// up on the wrong card state etc.).
|
||||
if (wasFacedown && (fromBattlefield || (toHand && zoneFrom.is(ZoneType.Exile)))) {
|
||||
c.turnFaceUp(false, false);
|
||||
c.forceTurnFaceUp();
|
||||
}
|
||||
|
||||
if (!c.isToken()) {
|
||||
@@ -233,17 +231,20 @@ public class GameAction {
|
||||
for (final StaticAbility sa : copied.getStaticAbilities()) {
|
||||
sa.setHostCard(copied);
|
||||
}
|
||||
if (c.getName().equals("Skullbriar, the Walking Grave")) {
|
||||
copied.setCounters(c.getCounters());
|
||||
}
|
||||
|
||||
// ensure that any leftover keyword/type changes are cleared in the state view
|
||||
copied.updateStateForView();
|
||||
} else { //Token
|
||||
copied = c;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that any leftover keyword/type changes are cleared in the state view
|
||||
copied.updateStateForView();
|
||||
|
||||
// Clean up temporary variables such as Sunburst value or announced PayX value
|
||||
if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) {
|
||||
copied.clearTemporaryVars();
|
||||
}
|
||||
|
||||
|
||||
if (!suppress) {
|
||||
if (zoneFrom == null) {
|
||||
copied.getOwner().addInboundToken(copied);
|
||||
@@ -263,7 +264,7 @@ public class GameAction {
|
||||
if (repres != ReplacementResult.NotReplaced) {
|
||||
// reset failed manifested Cards back to original
|
||||
if (c.isManifested() && !c.isInZone(ZoneType.Battlefield)) {
|
||||
c.turnFaceUp(false, false);
|
||||
c.forceTurnFaceUp();
|
||||
}
|
||||
|
||||
copied.getOwner().removeInboundToken(copied);
|
||||
@@ -389,7 +390,8 @@ public class GameAction {
|
||||
// play the change zone sound
|
||||
game.fireEvent(new GameEventCardChangeZone(c, zoneFrom, zoneTo));
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(lastKnownInfo);
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(copied);
|
||||
runParams.put(AbilityKey.CardLKI, lastKnownInfo);
|
||||
runParams.put(AbilityKey.Cause, cause);
|
||||
runParams.put(AbilityKey.Origin, zoneFrom != null ? zoneFrom.getZoneType().name() : null);
|
||||
runParams.put(AbilityKey.Destination, zoneTo.getZoneType().name());
|
||||
@@ -419,13 +421,6 @@ public class GameAction {
|
||||
return copied;
|
||||
}
|
||||
|
||||
// remove all counters from the card if destination is not the battlefield
|
||||
// UNLESS we're dealing with Skullbriar, the Walking Grave
|
||||
if (!c.isToken() && (zoneTo.is(ZoneType.Hand) || zoneTo.is(ZoneType.Library) ||
|
||||
(!toBattlefield && !c.getName().equals("Skullbriar, the Walking Grave")))) {
|
||||
copied.clearCounters();
|
||||
}
|
||||
|
||||
if (!c.isToken() && !toBattlefield) {
|
||||
copied.clearDevoured();
|
||||
copied.clearDelved();
|
||||
@@ -436,7 +431,7 @@ public class GameAction {
|
||||
// rule 504.6: reveal a face-down card leaving the stack
|
||||
if (zoneFrom != null && zoneTo != null && zoneFrom.is(ZoneType.Stack) && !zoneTo.is(ZoneType.Battlefield) && wasFacedown) {
|
||||
Card revealLKI = CardUtil.getLKICopy(c);
|
||||
revealLKI.turnFaceUp(true, false);
|
||||
revealLKI.forceTurnFaceUp();
|
||||
reveal(new CardCollection(revealLKI), revealLKI.getOwner(), true, "Face-down card moves from the stack: ");
|
||||
}
|
||||
|
||||
@@ -467,7 +462,7 @@ public class GameAction {
|
||||
// Reveal if face-down
|
||||
if (wasFacedown) {
|
||||
Card revealLKI = CardUtil.getLKICopy(c);
|
||||
revealLKI.turnFaceUp(true, false);
|
||||
revealLKI.forceTurnFaceUp();
|
||||
|
||||
reveal(new CardCollection(revealLKI), revealLKI.getOwner(), true, "Face-down card leaves the battlefield: ");
|
||||
|
||||
@@ -547,9 +542,10 @@ public class GameAction {
|
||||
c.setCastSA(null);
|
||||
} else if (zoneTo.is(ZoneType.Stack)) {
|
||||
c.setCastFrom(zoneFrom.getZoneType());
|
||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard()) && !c.isCopiedSpell()) {
|
||||
if (cause != null && cause.isSpell() && c.equals(cause.getHostCard()) && !c.isCopiedSpell()) {
|
||||
cause.setLastStateBattlefield(game.getLastStateBattlefield());
|
||||
cause.setLastStateGraveyard(game.getLastStateGraveyard());
|
||||
|
||||
c.setCastSA(cause);
|
||||
} else {
|
||||
c.setCastSA(null);
|
||||
@@ -969,6 +965,9 @@ public class GameAction {
|
||||
|
||||
for (final Player p : game.getPlayers()) {
|
||||
for (final ZoneType zt : ZoneType.values()) {
|
||||
if (zt == ZoneType.Command)
|
||||
p.checkKeywordCard();
|
||||
|
||||
if (zt == ZoneType.Battlefield) {
|
||||
continue;
|
||||
}
|
||||
@@ -984,7 +983,7 @@ public class GameAction {
|
||||
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||
if (c.isCreature()) {
|
||||
// Rule 704.5f - Put into grave (no regeneration) for toughness <= 0
|
||||
if (c.getLethal() <= 0) {
|
||||
if (c.getNetToughness() <= 0) {
|
||||
if (noRegCreats == null) {
|
||||
noRegCreats = new CardCollection();
|
||||
}
|
||||
@@ -1004,7 +1003,7 @@ public class GameAction {
|
||||
}
|
||||
// Rule 704.5g - Destroy due to lethal damage
|
||||
// Rule 704.5h - Destroy due to deathtouch
|
||||
else if (c.getLethal() <= c.getDamage() || c.hasBeenDealtDeathtouchDamage()) {
|
||||
else if (c.getDamage() > 0 && (c.getLethal() <= c.getDamage() || c.hasBeenDealtDeathtouchDamage())) {
|
||||
if (desCreats == null) {
|
||||
desCreats = new CardCollection();
|
||||
}
|
||||
@@ -1024,8 +1023,10 @@ public class GameAction {
|
||||
|
||||
checkAgain |= stateBasedAction704_5r(c); // annihilate +1/+1 counters with -1/-1 ones
|
||||
|
||||
if (c.getCounters(CounterType.DREAM) > 7 && c.hasKeyword("CARDNAME can't have more than seven dream counters on it.")) {
|
||||
c.subtractCounter(CounterType.DREAM, c.getCounters(CounterType.DREAM) - 7);
|
||||
final CounterType dreamType = CounterType.get(CounterEnumType.DREAM);
|
||||
|
||||
if (c.getCounters(dreamType) > 7 && c.hasKeyword("CARDNAME can't have more than seven dream counters on it.")) {
|
||||
c.subtractCounter(dreamType, c.getCounters(dreamType) - 7);
|
||||
checkAgain = true;
|
||||
}
|
||||
}
|
||||
@@ -1117,7 +1118,7 @@ public class GameAction {
|
||||
if (!c.canBeSacrificed()) {
|
||||
return false;
|
||||
}
|
||||
if (c.getCounters(CounterType.LORE) < c.getFinalChapterNr()) {
|
||||
if (c.getCounters(CounterEnumType.LORE) < c.getFinalChapterNr()) {
|
||||
return false;
|
||||
}
|
||||
if (!game.getStack().hasSimultaneousStackEntries() &&
|
||||
@@ -1158,16 +1159,18 @@ public class GameAction {
|
||||
|
||||
private boolean stateBasedAction704_5r(Card c) {
|
||||
boolean checkAgain = false;
|
||||
int plusOneCounters = c.getCounters(CounterType.P1P1);
|
||||
int minusOneCounters = c.getCounters(CounterType.M1M1);
|
||||
final CounterType p1p1 = CounterType.get(CounterEnumType.P1P1);
|
||||
final CounterType m1m1 = CounterType.get(CounterEnumType.M1M1);
|
||||
int plusOneCounters = c.getCounters(p1p1);
|
||||
int minusOneCounters = c.getCounters(m1m1);
|
||||
if (plusOneCounters > 0 && minusOneCounters > 0) {
|
||||
int remove = Math.min(plusOneCounters, minusOneCounters);
|
||||
// If a permanent has both a +1/+1 counter and a -1/-1 counter on it,
|
||||
// N +1/+1 and N -1/-1 counters are removed from it, where N is the
|
||||
// smaller of the number of +1/+1 and -1/-1 counters on it.
|
||||
// This should fire remove counters trigger
|
||||
c.subtractCounter(CounterType.P1P1, remove);
|
||||
c.subtractCounter(CounterType.M1M1, remove);
|
||||
c.subtractCounter(p1p1, remove);
|
||||
c.subtractCounter(m1m1, remove);
|
||||
checkAgain = true;
|
||||
}
|
||||
return checkAgain;
|
||||
@@ -1178,7 +1181,7 @@ public class GameAction {
|
||||
boolean checkAgain = false;
|
||||
if (c.isToken()) {
|
||||
final Zone zoneFrom = game.getZoneOf(c);
|
||||
if (!zoneFrom.is(ZoneType.Battlefield) && !zoneFrom.is(ZoneType.Command)) {
|
||||
if (!zoneFrom.is(ZoneType.Battlefield)) {
|
||||
zoneFrom.remove(c);
|
||||
checkAgain = true;
|
||||
}
|
||||
@@ -1288,7 +1291,7 @@ public class GameAction {
|
||||
//final Multimap<String, Card> uniqueWalkers = ArrayListMultimap.create(); // Not used as of Ixalan
|
||||
|
||||
for (Card c : list) {
|
||||
if (c.getCounters(CounterType.LOYALTY) <= 0) {
|
||||
if (c.getCounters(CounterEnumType.LOYALTY) <= 0) {
|
||||
sacrificeDestroy(c, null, table);
|
||||
// Play the Destroy sound
|
||||
game.fireEvent(new GameEventCardDestroyed());
|
||||
@@ -1348,7 +1351,8 @@ public class GameAction {
|
||||
|
||||
recheck = true;
|
||||
|
||||
Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new AbilitySub(ApiType.InternalLegendaryRule, null, null, null), "You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)");
|
||||
Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, null, p),
|
||||
"You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
|
||||
for (Card c: cc) {
|
||||
if (c != toKeep) {
|
||||
sacrificeDestroy(c, null, table);
|
||||
@@ -1460,12 +1464,18 @@ public class GameAction {
|
||||
revealTo(card, Collections.singleton(to));
|
||||
}
|
||||
public void revealTo(final CardCollectionView cards, final Player to) {
|
||||
revealTo(cards, Collections.singleton(to));
|
||||
revealTo(cards, to, null);
|
||||
}
|
||||
public void revealTo(final CardCollectionView cards, final Player to, String messagePrefix) {
|
||||
revealTo(cards, Collections.singleton(to), messagePrefix);
|
||||
}
|
||||
public void revealTo(final Card card, final Iterable<Player> to) {
|
||||
revealTo(new CardCollection(card), to);
|
||||
}
|
||||
public void revealTo(final CardCollectionView cards, final Iterable<Player> to) {
|
||||
revealTo(cards, to, null);
|
||||
}
|
||||
public void revealTo(final CardCollectionView cards, final Iterable<Player> to, String messagePrefix) {
|
||||
if (cards.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -1473,7 +1483,7 @@ public class GameAction {
|
||||
final ZoneType zone = cards.getFirst().getZone().getZoneType();
|
||||
final Player owner = cards.getFirst().getOwner();
|
||||
for (final Player p : to) {
|
||||
p.getController().reveal(cards, zone, owner);
|
||||
p.getController().reveal(cards, zone, owner, messagePrefix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user