From fd01dc1bb1d36d4e274112205da509f14159f1c0 Mon Sep 17 00:00:00 2001
From: Bug Hunter
Date: Fri, 26 Mar 2021 10:58:22 +0000
Subject: [PATCH] AI Multiplayer improvements
---
.../java/forge/ai/AiAttackController.java | 19 +-
.../src/main/java/forge/ai/ComputerUtil.java | 132 +++++--------
.../main/java/forge/ai/ComputerUtilCard.java | 2 +-
.../java/forge/ai/ComputerUtilCombat.java | 21 ++-
.../forge/ai/ability/ActivateAbilityAi.java | 5 +-
.../main/java/forge/ai/ability/BalanceAi.java | 15 +-
.../main/java/forge/ai/ability/BidLifeAi.java | 3 +-
.../java/forge/ai/ability/ChangeZoneAi.java | 23 +--
.../java/forge/ai/ability/ChooseCardAi.java | 3 +-
.../forge/ai/ability/ChooseCardNameAi.java | 3 +-
.../forge/ai/ability/ChooseEvenOddAi.java | 3 +-
.../java/forge/ai/ability/ChooseNumberAi.java | 3 +-
.../java/forge/ai/ability/ChooseSourceAi.java | 3 +-
.../forge/ai/ability/ControlExchangeAi.java | 3 +-
.../main/java/forge/ai/ability/DebuffAi.java | 17 +-
.../java/forge/ai/ability/DestroyAllAi.java | 178 +++++++++---------
.../src/main/java/forge/ai/ability/DigAi.java | 5 +-
.../java/forge/ai/ability/DigMultipleAi.java | 5 +-
.../java/forge/ai/ability/DigUntilAi.java | 7 +-
.../main/java/forge/ai/ability/DiscardAi.java | 9 +-
.../java/forge/ai/ability/DrainManaAi.java | 3 +-
.../java/forge/ai/ability/GameLossAi.java | 3 +-
.../java/forge/ai/ability/LifeExchangeAi.java | 5 +-
.../ai/ability/LifeExchangeVariantAi.java | 3 +-
.../main/java/forge/ai/ability/LifeSetAi.java | 19 +-
.../forge/ai/ability/PowerExchangeAi.java | 7 +-
.../java/forge/ai/ability/PumpAiBase.java | 3 +-
.../main/java/forge/ai/ability/PumpAllAi.java | 33 ++--
.../main/java/forge/ai/ability/RepeatAi.java | 6 +-
.../java/forge/ai/ability/SacrificeAi.java | 15 +-
.../java/forge/ai/ability/SacrificeAllAi.java | 52 ++---
.../src/main/java/forge/ai/ability/TapAi.java | 1 -
.../main/java/forge/ai/ability/TapAiBase.java | 3 +-
.../java/forge/ai/ability/TwoPilesAi.java | 3 +-
.../java/forge/ai/ability/UnattachAllAi.java | 5 +-
.../main/java/forge/ai/ability/UntapAi.java | 7 +-
.../src/main/java/forge/game/card/Card.java | 11 --
.../main/java/forge/game/player/Player.java | 3 +
.../res/cardsfolder/g/goblin_artisans.txt | 1 +
.../res/cardsfolder/h/hellion_eruption.txt | 3 +-
40 files changed, 287 insertions(+), 358 deletions(-)
diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java
index 5f2e126f872..c3ef8885a03 100644
--- a/forge-ai/src/main/java/forge/ai/AiAttackController.java
+++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java
@@ -91,7 +91,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, boolean nextTurn) {
this.ai = ai;
- this.defendingOpponent = choosePreferredDefenderPlayer();
+ this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>();
@@ -107,7 +107,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, Card attacker) {
this.ai = ai;
- this.defendingOpponent = choosePreferredDefenderPlayer();
+ this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>();
@@ -156,13 +156,12 @@ public class AiAttackController {
}
/** Choose opponent for AI to attack here. Expand as necessary. */
- private Player choosePreferredDefenderPlayer() {
- Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life
+ public static Player choosePreferredDefenderPlayer(Player ai) {
+ Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
- if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range
- return defender;
- } else { //Otherwise choose a random opponent to ensure no ganging up on players
- defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
+ if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
+ // TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
+ return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
}
return defender;
}
@@ -624,7 +623,7 @@ public class AiAttackController {
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp);
- if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai) >= opp.getLife()
+ if (totalCombatDamage + ComputerUtil.possibleNonCombatDamage(ai, opp) >= opp.getLife()
&& !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
return true;
}
@@ -919,7 +918,7 @@ public class AiAttackController {
// find the potential damage ratio the AI can cause
double humanLifeToDamageRatio = 1000000;
if (candidateUnblockedDamage > 0) {
- humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai)) / candidateUnblockedDamage;
+ humanLifeToDamageRatio = (double) (opp.getLife() - ComputerUtil.possibleNonCombatDamage(ai, opp)) / candidateUnblockedDamage;
}
// determine if the ai outnumbers the player
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
index 03dceacc08e..17fb2007fab 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java
@@ -188,7 +188,6 @@ public class ComputerUtil {
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
-
// Play higher costing spells first?
final Cost cost = sa.getPayCosts();
@@ -213,7 +212,8 @@ public class ComputerUtil {
if (unless != null && !unless.endsWith(">")) {
final int amount = AbilityUtils.calculateAmount(source, unless, sa);
- final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtil.getOpponentFor(ai), true).size();
+ // this is enough as long as the AI is only smart enough to target top of stack
+ final int usableManaSources = ComputerUtilMana.getAvailableManaSources(ComputerUtilAbility.getTopSpellAbilityOnStack(ai.getGame(), sa).getActivatingPlayer(), true).size();
// If the Unless isn't enough, this should be less likely to be used
if (amount > usableManaSources) {
@@ -1068,9 +1068,6 @@ public class ComputerUtil {
return true;
}
}
- if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
- return true;
- }
if (card.isCreature()) {
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
return true;
@@ -1093,8 +1090,8 @@ public class ComputerUtil {
} // BuffedBy
- // get all cards the human controls with AntiBuffedBy
- final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
+ // there's a good chance AI will attack weak target
+ final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
for (Card buffedcard : antibuffed) {
if (buffedcard.hasSVar("AntiBuffedBy")) {
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
@@ -1142,27 +1139,16 @@ public class ComputerUtil {
* @return true if it's OK to cast this Card for less than the max targets
*/
public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
- boolean ret = true;
- if (source.getManaCost().countX() > 0) {
- // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available.
- return ret;
- } else {
- // Otherwise, if life is possibly in danger, then this is fine.
- Combat combat = new Combat(ComputerUtil.getOpponentFor(ai));
- CardCollectionView attackers = ComputerUtil.getOpponentFor(ai).getCreaturesInPlay();
- for (Card att : attackers) {
- if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
- combat.addAttacker(att, ComputerUtil.getOpponentFor(att.getController()));
- }
- }
- AiBlockController aiBlock = new AiBlockController(ai);
- aiBlock.assignBlockersForCombat(combat);
- if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
- // Otherwise, return false. Do not play now.
- ret = false;
- }
+ if (source.getXManaCostPaid() > 0) {
+ // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by payment resources available.
+ return true;
}
- return ret;
+ if (aiLifeInDanger(ai, false, 0)) {
+ // Otherwise, if life is possibly in danger, then this is fine.
+ return true;
+ }
+ // do not play now.
+ return false;
}
/**
@@ -1266,8 +1252,8 @@ public class ComputerUtil {
}
}
- // get all cards the human controls with AntiBuffedBy
- final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield);
+ // there's a good chance AI will attack weak target
+ final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
for (Card buffedcard : antibuffed) {
if (buffedcard.hasSVar("AntiBuffedBy")) {
final String buffedby = buffedcard.getSVar("AntiBuffedBy");
@@ -1463,7 +1449,7 @@ public class ComputerUtil {
return false;
}
- public static int possibleNonCombatDamage(Player ai) {
+ public static int possibleNonCombatDamage(Player ai, Player enemy) {
int damage = 0;
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
all.addAll(ai.getCardsActivableInExternalZones(true));
@@ -1483,7 +1469,6 @@ public class ComputerUtil {
if (tgt == null) {
continue;
}
- final Player enemy = ComputerUtil.getOpponentFor(ai);
if (!sa.canTarget(enemy)) {
continue;
}
@@ -2346,7 +2331,7 @@ public class ComputerUtil {
}
}
else if (logic.equals("ChosenLandwalk")) {
- for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
+ for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
for (String t : c.getType()) {
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
chosen = t;
@@ -2364,7 +2349,7 @@ public class ComputerUtil {
else if (kindOfType.equals("Land")) {
if (logic != null) {
if (logic.equals("ChosenLandwalk")) {
- for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) {
+ for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
for (String t : c.getType().getLandTypes()) {
if (!invalidTypes.contains(t)) {
chosen = t;
@@ -2399,23 +2384,26 @@ public class ComputerUtil {
case "Torture":
return "Torture";
case "GraceOrCondemnation":
- return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
- : "Condemnation";
+ List graceZones = new ArrayList();
+ graceZones.add(ZoneType.Battlefield);
+ graceZones.add(ZoneType.Graveyard);
+ CardCollection graceCreatures = CardLists.getType(sa.getHostCard().getGame().getCardsIn(graceZones), "Creature");
+ int humanGrace = CardLists.filterControlledBy(graceCreatures, ai.getOpponents()).size();
+ int aiGrace = CardLists.filterControlledBy(graceCreatures, ai).size();
+ return aiGrace > humanGrace ? "Grace" : "Condemnation";
case "CarnageOrHomage":
- CardCollection cardsInPlay = CardLists
- .getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
+ CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
- CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai);
- return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard
- .evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
+ CardCollection computerlist = ai.getCreaturesInPlay();
+ return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
case "Judgment":
if (votes.isEmpty()) {
CardCollection list = new CardCollection();
for (Object o : options) {
if (o instanceof Card) {
list.add((Card) o);
- }
}
+ }
return ComputerUtilCard.getBestAI(list);
} else {
return Iterables.getFirst(votes.keySet(), null);
@@ -2934,23 +2922,6 @@ public class ComputerUtil {
return true;
}
-
- @Deprecated
- public static final Player getOpponentFor(final Player player) {
- // This method is deprecated and currently functions as a synonym for player.getWeakestOpponent
- // until it can be replaced everywhere in the code.
-
- // Consider replacing calls to this method either with a multiplayer-friendly determination of
- // opponent that contextually makes the most sense, or with a direct call to player.getWeakestOpponent
- // where that is applicable and makes sense from the point of view of multiplayer AI logic.
- Player opponent = player.getWeakestOpponent();
- if (opponent != null) {
- return opponent;
- }
-
- throw new IllegalStateException("No opponents left ingame for " + player);
- }
-
public static int countUsefulCreatures(Player p) {
CardCollection creats = p.getCreaturesInPlay();
int count = 0;
@@ -3033,31 +3004,32 @@ public class ComputerUtil {
// call this to determine if it's safe to use a life payment spell
// or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
- Player opponent = ComputerUtil.getOpponentFor(ai);
- // test whether the human can kill the ai next turn
- Combat combat = new Combat(opponent);
- boolean containsAttacker = false;
- for (Card att : opponent.getCreaturesInPlay()) {
- if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
- combat.addAttacker(att, ai);
- containsAttacker = true;
+ for (Player opponent: ai.getOpponents()) {
+ // test whether the human can kill the ai next turn
+ Combat combat = new Combat(opponent);
+ boolean containsAttacker = false;
+ for (Card att : opponent.getCreaturesInPlay()) {
+ if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
+ combat.addAttacker(att, ai);
+ containsAttacker = true;
+ }
}
- }
- if (!containsAttacker) {
- return false;
- }
- AiBlockController block = new AiBlockController(ai);
- block.assignBlockersForCombat(combat);
+ if (!containsAttacker) {
+ return false;
+ }
+ AiBlockController block = new AiBlockController(ai);
+ block.assignBlockersForCombat(combat);
- // TODO predict other, noncombat sources of damage and add them to the "payment" variable.
- // examples : Black Vise, The Rack, known direct damage spells in enemy hand, etc
- // If added, might need a parameter to define whether we want to check all threats or combat threats.
+ // TODO predict other, noncombat sources of damage and add them to the "payment" variable.
+ // examples : Black Vise, The Rack, known direct damage spells in enemy hand, etc
+ // If added, might need a parameter to define whether we want to check all threats or combat threats.
- if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) {
- return true;
- }
- if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
- return true;
+ if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) {
+ return true;
+ }
+ if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
+ return true;
+ }
}
return false;
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
index 00433b71a17..19172312c04 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java
@@ -550,7 +550,7 @@ public class ComputerUtilCard {
*/
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
AiBlockController aiBlk = new AiBlockController(ai);
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
Combat combat = new Combat(opp);
//Use actual attackers if available, else consider all possible attackers
Combat currentCombat = ai.getGame().getCombat();
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
index d4b7d549f74..ebcde781c0e 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java
@@ -97,34 +97,39 @@ public class ComputerUtilCombat {
* canAttackNextTurn.
*
*
- * @param atacker
+ * @param attacker
* a {@link forge.game.card.Card} object.
* @param defender
* the defending {@link GameEntity}.
* @return a boolean.
*/
- public static boolean canAttackNextTurn(final Card atacker, final GameEntity defender) {
- if (!atacker.isCreature()) {
+ public static boolean canAttackNextTurn(final Card attacker, final GameEntity defender) {
+ if (!attacker.isCreature()) {
return false;
}
- if (!CombatUtil.canAttackNextTurn(atacker, defender)) {
+ if (!CombatUtil.canAttackNextTurn(attacker, defender)) {
return false;
}
- for (final KeywordInterface inst : atacker.getKeywords()) {
+ for (final KeywordInterface inst : attacker.getKeywords()) {
final String keyword = inst.getOriginal();
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
final String defined = keyword.split(":")[1];
- final Player player = AbilityUtils.getDefinedPlayers(atacker, defined, null).get(0);
+ final Player player = AbilityUtils.getDefinedPlayers(attacker, defined, null).get(0);
if (!defender.equals(player)) {
return false;
}
}
}
+ // TODO this should be a factor but needs some alignment with AttachAi
+ //boolean leavesPlay = !ComputerUtilCard.hasActiveUndyingOrPersist(attacker)
+ // && ((attacker.hasKeyword(Keyword.VANISHING) && attacker.getCounters(CounterEnumType.TIME) == 1)
+ // || (attacker.hasKeyword(Keyword.FADING) && attacker.getCounters(CounterEnumType.FADE) == 0)
+ // || attacker.hasSVar("EndOfTurnLeavePlay"));
// The creature won't untap next turn
- return !atacker.isTapped() || Untap.canUntap(atacker);
- } // canAttackNextTurn(Card, GameEntity)
+ return !attacker.isTapped() || Untap.canUntap(attacker);
+ }
/**
*
diff --git a/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java b/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java
index e261dab37db..3340761aff4 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java
@@ -22,7 +22,6 @@ public class ActivateAbilityAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard();
final Player opp = ai.getWeakestOpponent();
- boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
if (list.isEmpty()) {
@@ -40,6 +39,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
sa.getTargets().add(opp);
}
+ boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return randomReturn;
}
@@ -56,7 +56,8 @@ public class ActivateAbilityAi extends SpellAbilityAi {
} else {
final List defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
- return defined.contains(opp);
+ // if at least two players are returned we can affect another opponent
+ return defined.size() > 1 || defined.contains(opp);
}
} else {
diff --git a/forge-ai/src/main/java/forge/ai/ability/BalanceAi.java b/forge-ai/src/main/java/forge/ai/ability/BalanceAi.java
index 9aba6e53fe9..902741c6da3 100644
--- a/forge-ai/src/main/java/forge/ai/ability/BalanceAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/BalanceAi.java
@@ -13,18 +13,21 @@ public class BalanceAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic");
-
int diff = 0;
- // TODO Add support for multiplayer logic
- final Player opp = aiPlayer.getWeakestOpponent();
- final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
+ Player opp = aiPlayer.getWeakestOpponent();
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield);
+ for (Player min : aiPlayer.getOpponents()) {
+ if (min.getCardsIn(ZoneType.Battlefield).size() < opp.getCardsIn(ZoneType.Battlefield).size()) {
+ opp = min;
+ }
+ }
+ final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
if ("BalanceCreaturesAndLands".equals(logic)) {
- // Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting
+ // TODO Copied over from hardcoded Balance. We should be checking value of the lands/creatures for each opponent, not just counting
diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size();
- diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
+ diff += 1.5 * (CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() -
CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
}
else if ("BalancePermanents".equals(logic)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/BidLifeAi.java b/forge-ai/src/main/java/forge/ai/ability/BidLifeAi.java
index 634fabc1254..6f7b6e943d6 100644
--- a/forge-ai/src/main/java/forge/ai/ability/BidLifeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/BidLifeAi.java
@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canTgtCreature()) {
- List list = CardLists.getTargetableCards(aiPlayer.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), sa);
+ List list = CardLists.getTargetableCards(AiAttackController.choosePreferredDefenderPlayer(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty()) {
return false;
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
index 58ac2740907..51c19811578 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
@@ -15,6 +15,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import forge.ai.AiAttackController;
import forge.ai.AiBlockController;
import forge.ai.AiCardMemory;
import forge.ai.AiController;
@@ -263,7 +264,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
ZoneType origin = null;
- final Player opponent = ai.getWeakestOpponent();
+ final Player opponent = AiAttackController.choosePreferredDefenderPlayer(ai);
boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (sa.hasParam("Origin")) {
@@ -471,7 +472,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// if putting cards from hand to library and parent is drawing cards
// make sure this will actually do something:
final TargetRestrictions tgt = sa.getTargetRestrictions();
- final Player opp = aiPlayer.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (tgt != null && tgt.canTgtPlayer()) {
boolean isCurse = sa.isCurse();
if (isCurse && sa.canTarget(opp)) {
@@ -530,7 +531,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
Iterable pDefined;
final TargetRestrictions tgt = sa.getTargetRestrictions();
if ((tgt != null) && tgt.canTgtPlayer()) {
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.isCurse()) {
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
@@ -892,7 +893,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
sa.setXManaCostPaid(xPay);
- // TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
}
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
@@ -913,9 +913,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (sa.isSpell()) {
list.remove(source); // spells can't target their own source, because it's actually in the stack zone
}
- //System.out.println("isPreferredTarget " + list);
if (sa.hasParam("AttachedTo")) {
- //System.out.println("isPreferredTarget att " + list);
list = CardLists.filter(list, new Predicate() {
@Override
public boolean apply(final Card c) {
@@ -927,7 +925,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false;
}
});
- //System.out.println("isPreferredTarget ok " + list);
}
if (list.size() < sa.getMinTargets()) {
@@ -1482,9 +1479,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
if ("DeathgorgeScavenger".equals(logic)) {
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
- } else if ("ExtraplanarLens".equals(logic)) {
+ }
+ if ("ExtraplanarLens".equals(logic)) {
return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
- } else if ("ExileCombatThreat".equals(logic)) {
+ }
+ if ("ExileCombatThreat".equals(logic)) {
return doExileCombatThreatLogic(ai, sa);
}
@@ -1984,11 +1983,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
}
- if (toPay == 0) {
- canBeSaved.add(potentialTgt);
- }
-
- if (toPay <= usableManaSources) {
+ if (toPay == 0 || toPay <= usableManaSources) {
canBeSaved.add(potentialTgt);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
index a50ee8c6def..3dd7825cad3 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java
@@ -9,6 +9,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
@@ -114,7 +115,7 @@ public class ChooseCardAi extends SpellAbilityAi {
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
} else if (aiLogic.equals("Duneblast")) {
CardCollection aiCreatures = ai.getCreaturesInPlay();
- CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay();
+ CardCollection oppCreatures = AiAttackController.choosePreferredDefenderPlayer(ai).getCreaturesInPlay();
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java
index 469a095fa4e..b12f37aecca 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java
@@ -7,6 +7,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.StaticData;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpecialCardAi;
@@ -44,7 +45,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
if (tgt != null) {
sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) {
- sa.getTargets().add(ai.getWeakestOpponent());
+ sa.getTargets().add(AiAttackController.choosePreferredDefenderPlayer(ai));
} else {
sa.getTargets().add(ai);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
index e5ac8a99d6d..2a6e465865a 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseEvenOddAi.java
@@ -1,5 +1,6 @@
package forge.ai.ability;
+import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -16,7 +17,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
- Player opp = aiPlayer.getWeakestOpponent();
+ Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java
index 46276414778..639baa92c40 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java
@@ -1,5 +1,6 @@
package forge.ai.ability;
+import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
- Player opp = aiPlayer.getWeakestOpponent();
+ Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
index 1396a1bd6dd..8a71e557d4d 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChooseSourceAi.java
@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi;
@@ -54,7 +55,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
- Player opp = ai.getWeakestOpponent();
+ Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.canTarget(opp)) {
sa.getTargets().add(opp);
} else {
diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java
index 57d56de1759..65f11c69b35 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java
@@ -3,6 +3,7 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
sa.resetTargets();
CardCollection list =
- CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
+ CardLists.getValidCards(AiAttackController.choosePreferredDefenderPlayer(ai).getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
// AI won't try to grab cards that are filtered out of AI decks on
// purpose
list = CardLists.filter(list, new Predicate() {
diff --git a/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java b/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java
index 4a28f540a1e..76263212db2 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DebuffAi.java
@@ -8,6 +8,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
@@ -26,9 +27,6 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class DebuffAi extends SpellAbilityAi {
- // *************************************************************************
- // ***************************** Debuff ************************************
- // *************************************************************************
@Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) {
@@ -140,7 +138,6 @@ public class DebuffAi extends SpellAbilityAi {
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null;
- // boolean goodt = false;
if (list.isEmpty()) {
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) {
@@ -176,19 +173,18 @@ public class DebuffAi extends SpellAbilityAi {
* @return a CardCollection.
*/
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List kws) {
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate() {
@Override
public boolean apply(final Card c) {
- return c.hasAnyKeyword(kws); // don't add duplicate negative
- // keywords
+ return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
}
});
}
return list;
- } // getCurseCreatures()
+ }
/**
*
@@ -216,7 +212,7 @@ public class DebuffAi extends SpellAbilityAi {
list.remove(c);
}
- final CardCollection pref = CardLists.filterControlledBy(list, ai.getWeakestOpponent());
+ final CardCollection pref = CardLists.filterControlledBy(list, ai.getOpponents());
final CardCollection forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard();
@@ -242,8 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
break;
}
- // TODO - if forced targeting, just pick something without the given
- // keyword
+ // TODO - if forced targeting, just pick something without the given keyword
Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced);
diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
index 13f1a975b53..79f88577204 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAllAi.java
@@ -70,12 +70,12 @@ public class DestroyAllAi extends SpellAbilityAi {
return doMassRemovalLogic(ai, sa);
}
- public boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
+ public static boolean doMassRemovalLogic(Player ai, SpellAbility sa) {
final Card source = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", "");
- Player opponent = ai.getWeakestOpponent(); // TODO: how should this AI logic work for multiplayer and getOpponents()?
- final int CREATURE_EVAL_THRESHOLD = 200;
+ // if we hit the whole board, the other opponents who are not the reason to cast this probably still suffer a bit too
+ final int CREATURE_EVAL_THRESHOLD = 200 / (!sa.usesTargeting() ? ai.getOpponents().size() : 1);
if (logic.equals("Always")) {
return true; // e.g. Tetzimoc, Primal Death, where we want to cast the permanent even if the removal trigger does nothing
@@ -93,99 +93,101 @@ public class DestroyAllAi extends SpellAbilityAi {
valid = valid.replace("X", Integer.toString(xPay));
}
- CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield),
- valid.split(","), source.getController(), source, sa);
- CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","),
- source.getController(), source, sa);
-
- opplist = CardLists.filter(opplist, predicate);
- ailist = CardLists.filter(ailist, predicate);
- if (opplist.isEmpty()) {
- return false;
- }
+ // TODO should probably sort results when targeted to use on biggest threat instead of first match
+ for (Player opponent: ai.getOpponents()) {
+ CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
+ CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
- if (sa.usesTargeting()) {
- sa.resetTargets();
- if (sa.canTarget(opponent)) {
- sa.getTargets().add(opponent);
- ailist.clear();
- } else {
+ opplist = CardLists.filter(opplist, predicate);
+ ailist = CardLists.filter(ailist, predicate);
+ if (opplist.isEmpty()) {
return false;
}
- }
- // Special handling for Raiding Party
- if (logic.equals("RaidingParty")) {
- int numAiCanSave = Math.min(CardLists.filter(ai.getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, ailist.size());
- int numOppsCanSave = Math.min(CardLists.filter(ai.getOpponents().getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, opplist.size());
-
- return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave);
- }
-
- // If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
- if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
- && (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
- return true;
- }
-
- // If effect is destroying creatures and AI is about to get low on life, activate effect anyway if difference in lost permanents not very much
- if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
- && (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat()))
- && ((ComputerUtilCard.evaluatePermanentList(ailist) - 6) >= ComputerUtilCard.evaluatePermanentList(opplist))) {
- return true;
- }
-
- // if only creatures are affected evaluate both lists and pass only if
- // human creatures are more valuable
- if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
- if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
- return true;
- }
-
- if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
- return false;
- }
-
- // test whether the human can kill the ai next turn
- Combat combat = new Combat(opponent);
- boolean containsAttacker = false;
- for (Card att : opponent.getCreaturesInPlay()) {
- if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
- combat.addAttacker(att, ai);
- containsAttacker = containsAttacker | opplist.contains(att);
- }
- }
- if (!containsAttacker) {
- return false;
- }
- AiBlockController block = new AiBlockController(ai);
- block.assignBlockersForCombat(combat);
-
- if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
- return true;
- }
- return false;
- } // only lands involved
- else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
- if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds") && !opplist.isEmpty()) {
- return true;
- }
- // evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
- CardCollection aiCreatures = ai.getCreaturesInPlay();
- CardCollection oppCreatures = opponent.getCreaturesInPlay();
- if (!oppCreatures.isEmpty()) {
- if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
+ if (sa.usesTargeting()) {
+ sa.resetTargets();
+ if (sa.canTarget(opponent)) {
+ sa.getTargets().add(opponent);
+ ailist.clear();
+ } else {
return false;
}
}
- // check if the AI would lose more lands than the opponent would
- if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
+
+ // Special handling for Raiding Party
+ if (logic.equals("RaidingParty")) {
+ int numAiCanSave = Math.min(CardLists.filter(ai.getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, ailist.size());
+ int numOppsCanSave = Math.min(CardLists.filter(ai.getOpponents().getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, opplist.size());
+
+ return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave);
+ }
+
+ // If effect is destroying creatures and AI is about to lose, activate effect anyway no matter what!
+ if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
+ && (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInSeriousDanger(ai, ai.getGame().getCombat()))) {
+ return true;
+ }
+
+ // If effect is destroying creatures and AI is about to get low on life, activate effect anyway if difference in lost permanents not very much
+ if ((!CardLists.getType(opplist, "Creature").isEmpty()) && (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS))
+ && (ai.getGame().getCombat() != null && ComputerUtilCombat.lifeInDanger(ai, ai.getGame().getCombat()))
+ && ((ComputerUtilCard.evaluatePermanentList(ailist) - 6) >= ComputerUtilCard.evaluatePermanentList(opplist))) {
+ return true;
+ }
+
+ // if only creatures are affected evaluate both lists and pass only if human creatures are more valuable
+ if (CardLists.getNotType(opplist, "Creature").isEmpty() && CardLists.getNotType(ailist, "Creature").isEmpty()) {
+ if (ComputerUtilCard.evaluateCreatureList(ailist) + CREATURE_EVAL_THRESHOLD < ComputerUtilCard.evaluateCreatureList(opplist)) {
+ return true;
+ }
+
+ if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) {
+ return false;
+ }
+
+ // test whether the human can kill the ai next turn
+ Combat combat = new Combat(opponent);
+ boolean containsAttacker = false;
+ for (Card att : opponent.getCreaturesInPlay()) {
+ if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
+ combat.addAttacker(att, ai);
+ containsAttacker = containsAttacker | opplist.contains(att);
+ }
+ }
+ if (!containsAttacker) {
+ return false;
+ }
+ AiBlockController block = new AiBlockController(ai);
+ block.assignBlockersForCombat(combat);
+
+ if (ComputerUtilCombat.lifeInSeriousDanger(ai, combat)) {
+ return true;
+ }
+ return false;
+ } // only lands involved
+ else if (CardLists.getNotType(opplist, "Land").isEmpty() && CardLists.getNotType(ailist, "Land").isEmpty()) {
+ if (ai.isCardInPlay("Crucible of Worlds") && !opponent.isCardInPlay("Crucible of Worlds")) {
+ return true;
+ }
+ // evaluate the situation with creatures on the battlefield separately, as that's where the AI typically makes mistakes
+ CardCollection aiCreatures = ai.getCreaturesInPlay();
+ CardCollection oppCreatures = opponent.getCreaturesInPlay();
+ if (!oppCreatures.isEmpty()) {
+ if (ComputerUtilCard.evaluateCreatureList(aiCreatures) < ComputerUtilCard.evaluateCreatureList(oppCreatures) + CREATURE_EVAL_THRESHOLD) {
+ return false;
+ }
+ }
+ // check if the AI would lose more lands than the opponent would
+ if (ComputerUtilCard.evaluatePermanentList(ailist) > ComputerUtilCard.evaluatePermanentList(opplist) + 1) {
+ return false;
+ }
+ } // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
+ else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
return false;
}
- } // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable
- else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
- return false;
+ return true;
}
- return true;
+ return false;
}
-}
+
+}
\ No newline at end of file
diff --git a/forge-ai/src/main/java/forge/ai/ability/DigAi.java b/forge-ai/src/main/java/forge/ai/ability/DigAi.java
index 4d7ffc1f458..33e3c315dc8 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DigAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DigAi.java
@@ -5,6 +5,7 @@ import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
@@ -32,7 +33,7 @@ public class DigAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
- Player opp = ai.getWeakestOpponent();
+ Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Card host = sa.getHostCard();
Player libraryOwner = ai;
@@ -120,7 +121,7 @@ public class DigAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final SpellAbility root = sa.getRootAbility();
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java b/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java
index 07796e6fba9..1e3b0350408 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java
@@ -1,5 +1,6 @@
package forge.ai.ability;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
@@ -19,7 +20,7 @@ public class DigMultipleAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame();
- Player opp = ai.getWeakestOpponent();
+ Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Card host = sa.getHostCard();
Player libraryOwner = ai;
@@ -77,7 +78,7 @@ public class DigMultipleAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) {
sa.resetTargets();
if (mandatory && sa.canTarget(opp)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
index 449561f46bf..d421a67f54d 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java
@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
@@ -31,10 +32,8 @@ public class DigUntilAi extends SpellAbilityAi {
chance = 1;
}
- final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
-
Player libraryOwner = ai;
- Player opp = ai.getWeakestOpponent();
+ Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if ("DontMillSelf".equals(logic)) {
// A card that digs for specific things and puts everything revealed before it into graveyard
@@ -92,12 +91,12 @@ public class DigUntilAi extends SpellAbilityAi {
return false;
}
+ final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
return randomReturn;
}
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
-
if (sa.usesTargeting()) {
sa.resetTargets();
if (sa.isCurse()) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java
index dd1e0fc35d6..17d69dcdbc0 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DiscardAi.java
@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCost;
@@ -60,11 +61,9 @@ public class DiscardAi extends SpellAbilityAi {
if (players.get(0) == ai) {
// the ai should only be using something like this if he has
// few cards in hand,
- // cards like this better have a good drawback to be in the
- // AIs deck
+ // cards like this better have a good drawback to be in the AIs deck
} else {
- // defined to the human, so that's fine as long the human
- // has cards
+ // defined to the human, so that's fine as long the human has cards
if (!humanHasHand) {
return false;
}
@@ -170,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
- Player opp = ai.getWeakestOpponent();
+ Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp);
diff --git a/forge-ai/src/main/java/forge/ai/ability/DrainManaAi.java b/forge-ai/src/main/java/forge/ai/ability/DrainManaAi.java
index 96310431fc3..870ae38bffd 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DrainManaAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DrainManaAi.java
@@ -23,8 +23,7 @@ public class DrainManaAi extends SpellAbilityAi {
if (tgt == null) {
// assume we are looking to tap human's stuff
- // TODO - check for things with untap abilities, and don't tap
- // those.
+ // TODO - check for things with untap abilities, and don't tap those.
final List defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/GameLossAi.java b/forge-ai/src/main/java/forge/ai/ability/GameLossAi.java
index 03b77143d99..8bdd71b1394 100644
--- a/forge-ai/src/main/java/forge/ai/ability/GameLossAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/GameLossAi.java
@@ -29,11 +29,10 @@ public class GameLossAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
-
// Phage the Untouchable
// (Final Fortune would need to attach it's delayed trigger to a
// specific turn, which can't be done yet)
- Player opp = ai.getWeakestOpponent();
+ Player opp = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
if (!mandatory && opp.cantLose()) {
return false;
diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java
index 8b387b7791a..2aba049fa22 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeAi.java
@@ -1,5 +1,6 @@
package forge.ai.ability;
+import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final int myLife = aiPlayer.getLife();
- Player opponent = aiPlayer.getWeakestOpponent();
+ Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
final int hLife = opponent.getLife();
if (!aiPlayer.canGainLife()) {
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
- Player opp = ai.getWeakestOpponent();
+ Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java
index 0fd3e1e0702..7c3147c3dfd 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java
@@ -1,5 +1,6 @@
package forge.ai.ability;
+import forge.ai.AiAttackController;
import forge.ai.AiProps;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
@@ -154,7 +155,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
- Player opp = ai.getWeakestOpponent();
+ Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (tgt != null) {
sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java
index 91e17c2da2f..cbf94b08d48 100644
--- a/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/LifeSetAi.java
@@ -17,7 +17,7 @@ public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
final int myLife = ai.getLife();
- final Player opponent = ai.getWeakestOpponent();
+ final Player opponent = ai.getStrongestOpponent();
final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount");
@@ -36,8 +36,7 @@ public class LifeSetAi extends SpellAbilityAi {
return false;
}
- // TODO handle proper calculation of X values based on Cost and what
- // would be paid
+ // TODO handle proper calculation of X values based on Cost and what would be paid
int amount;
// we shouldn't have to worry too much about PayX for SetLife
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
@@ -58,11 +57,9 @@ public class LifeSetAi extends SpellAbilityAi {
if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent);
// if we can only target the human, and the Human's life
- // would
- // go up, don't play it.
+ // would go up, don't play it.
// possibly add a combo here for Magister Sphinx and
- // Higedetsu's
- // (sp?) Second Rite
+ // Higedetsu's (sp?) Second Rite
if ((amount > hlife) || !opponent.canLoseLife()) {
return false;
}
@@ -81,8 +78,7 @@ public class LifeSetAi extends SpellAbilityAi {
if (sa.getParam("Defined").equals("Player")) {
if (amount == 0) {
return false;
- } else if (myLife > amount) { // will decrease computer's
- // life
+ } else if (myLife > amount) { // will decrease computer's life
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
return false;
}
@@ -104,7 +100,7 @@ public class LifeSetAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final int myLife = ai.getLife();
- final Player opponent = ai.getWeakestOpponent();
+ final Player opponent = ai.getStrongestOpponent();
final int hlife = opponent.getLife();
final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
@@ -133,8 +129,7 @@ public class LifeSetAi extends SpellAbilityAi {
}
// If the Target is gaining life, target self.
- // if the Target is modifying how much life is gained, this needs to
- // be handled better
+ // if the Target is modifying how much life is gained, this needs to be handled better
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
sa.resetTargets();
diff --git a/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java b/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java
index 9626b8f6f43..33d421d5626 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java
@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
sa.resetTargets();
List list =
- CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
- // AI won't try to grab cards that are filtered out of AI decks on
- // purpose
+ CardLists.getValidCards(ai.getGame().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa);
+ // AI won't try to grab cards that are filtered out of AI decks on purpose
list = CardLists.filter(list, new Predicate() {
@Override
public boolean apply(final Card c) {
- return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa);
+ return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa) && c.getController() != ai;
}
});
CardLists.sortByPowerAsc(list);
diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
index aba87269e83..0fc65c64997 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
@@ -201,7 +202,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
final Game game = ai.getGame();
final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler();
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final int newPower = card.getNetCombatDamage() + attack;
//int defense = getNumDefense(sa);
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java
index 381147880bd..386d043770d 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java
@@ -48,12 +48,6 @@ public class PumpAllAi extends PumpAiBase {
}
}
- final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
- final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
- final List keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
-
- final PhaseType phase = game.getPhaseHandler().getPhase();
-
if (ComputerUtil.preventRunAwayActivations(sa)) {
return false;
}
@@ -63,16 +57,10 @@ public class PumpAllAi extends PumpAiBase {
return false;
}
}
-
- if (sa.hasParam("ValidCards")) {
- valid = sa.getParam("ValidCards");
- }
-
- final Player opp = ai.getWeakestOpponent();
- CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
- CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
-
+
final TargetRestrictions tgt = sa.getTargetRestrictions();
+ final Player opp = ai.getStrongestOpponent();
+
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
sa.resetTargets();
sa.getTargets().add(opp);
@@ -85,6 +73,18 @@ public class PumpAllAi extends PumpAiBase {
return true;
}
+ final int power = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumAtt"), sa);
+ final int defense = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("NumDef"), sa);
+ final List keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
+ final PhaseType phase = game.getPhaseHandler().getPhase();
+
+ if (sa.hasParam("ValidCards")) {
+ valid = sa.getParam("ValidCards");
+ }
+
+ CardCollection comp = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
+ CardCollection human = CardLists.getValidCards(opp.getCardsIn(ZoneType.Battlefield), valid, source.getController(), source, sa);
+
if (!game.getStack().isEmpty() && !sa.isCurse()) {
return pumpAgainstRemoval(ai, sa, comp);
}
@@ -139,8 +139,7 @@ public class PumpAllAi extends PumpAiBase {
return true;
}
- // evaluate both lists and pass only if human creatures are more
- // valuable
+ // evaluate both lists and pass only if human creatures are more valuable
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
} // end Curse
diff --git a/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java b/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java
index 0f9c2a20ffe..310ea94efdf 100644
--- a/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java
@@ -1,6 +1,7 @@
package forge.ai.ability;
+import forge.ai.AiAttackController;
import forge.ai.AiController;
import forge.ai.ComputerUtilCost;
import forge.ai.PlayerControllerAi;
@@ -14,7 +15,7 @@ public class RepeatAi extends SpellAbilityAi {
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) {
if (!opp.canBeTargetedBy(sa)) {
@@ -44,9 +45,8 @@ public class RepeatAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
-
if (sa.usesTargeting()) {
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.canTarget(opp)) {
sa.resetTargets();
sa.getTargets().add(opp);
diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java
index dfd420dab29..36d0fa484c6 100644
--- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java
@@ -20,9 +20,6 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
public class SacrificeAi extends SpellAbilityAi {
- // **************************************************************
- // *************************** Sacrifice ***********************
- // **************************************************************
@Override
protected boolean canPlayAI(Player ai, SpellAbility sa) {
@@ -48,8 +45,7 @@ public class SacrificeAi extends SpellAbilityAi {
}
// Improve AI for triggers. If source is a creature with:
- // When ETB, sacrifice a creature. Check to see if the AI has something
- // to sacrifice
+ // When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice
// Eventually, we can call the trigger of ETB abilities with not
// mandatory as part of the checks to cast something
@@ -58,12 +54,11 @@ public class SacrificeAi extends SpellAbilityAi {
}
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
-
final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions();
final boolean destroy = sa.hasParam("Destroy");
- Player opp = ai.getWeakestOpponent();
+ Player opp = ai.getStrongestOpponent();
if (tgt != null) {
sa.resetTargets();
@@ -109,8 +104,7 @@ public class SacrificeAi extends SpellAbilityAi {
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
}
- final int half = (amount / 2) + (amount % 2); // Half of amount
- // rounded up
+ final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
// If the Human has at least half rounded up of the amount to be
// sacrificed, cast the spell
@@ -130,8 +124,7 @@ public class SacrificeAi extends SpellAbilityAi {
// If Sacrifice hits both players:
// Only cast it if Human has the full amount of valid
// Only cast it if AI doesn't have the full amount of Valid
- // TODO: Cast if the type is favorable: my "worst" valid is
- // worse than his "worst" valid
+ // TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
int amount = AbilityUtils.calculateAmount(source, num, sa);
diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java
index 84f496ef24e..11ebac1c75c 100644
--- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAllAi.java
@@ -2,17 +2,12 @@ package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost;
-import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
-import forge.game.card.CardCollection;
-import forge.game.card.CardLists;
import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
-import forge.game.zone.ZoneType;
import forge.util.MyRandom;
-import forge.util.TextUtil;
public class SacrificeAllAi extends SpellAbilityAi {
@@ -22,22 +17,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
// based on what the expected targets could be
final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard();
- String valid = "";
-
- if (sa.hasParam("ValidCards")) {
- valid = sa.getParam("ValidCards");
- }
-
- if (valid.contains("X") && sa.getSVar("X").equals("Count$xPaid")) {
- // Set PayX here to maximum value.
- final int xPay = ComputerUtilMana.determineLeftoverMana(sa, ai);
- valid = TextUtil.fastReplace(valid, "X", Integer.toString(xPay));
- }
-
- CardCollection humanlist =
- CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
- CardCollection computerlist =
- CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
+ final String logic = sa.getParamOrDefault("AILogic", "");
if (abCost != null) {
// AI currently disabled for some costs
@@ -45,30 +25,20 @@ public class SacrificeAllAi extends SpellAbilityAi {
return false;
}
}
+
+ if (logic.equals("HellionEruption")) {
+ if (ai.getCreaturesInPlay().size() < 5 || ai.getCreaturesInPlay().size() * 150 < ComputerUtilCard.evaluateCreatureList(ai.getCreaturesInPlay())) {
+ return false;
+ }
+ }
+
+ if (!DestroyAllAi.doMassRemovalLogic(ai, sa)) {
+ return false;
+ }
// prevent run-away activations - first time will always return true
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
- // if only creatures are affected evaluate both lists and pass only if
- // human creatures are more valuable
- if ((CardLists.getNotType(humanlist, "Creature").size() == 0) && (CardLists.getNotType(computerlist, "Creature").size() == 0)) {
- if ((ComputerUtilCard.evaluateCreatureList(computerlist) + 200) >= ComputerUtilCard
- .evaluateCreatureList(humanlist)) {
- return false;
- }
- } // only lands involved
- else if ((CardLists.getNotType(humanlist, "Land").size() == 0) && (CardLists.getNotType(computerlist, "Land").size() == 0)) {
- if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 1) >= ComputerUtilCard
- .evaluatePermanentList(humanlist)) {
- return false;
- }
- } // otherwise evaluate both lists by CMC and pass only if human
- // permanents are more valuable
- else if ((ComputerUtilCard.evaluatePermanentList(computerlist) + 3) >= ComputerUtilCard
- .evaluatePermanentList(humanlist)) {
- return false;
- }
-
return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAi.java b/forge-ai/src/main/java/forge/ai/ability/TapAi.java
index cb6428ee193..7216345b1f8 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TapAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TapAi.java
@@ -66,7 +66,6 @@ public class TapAi extends TapAiBase {
// Set PayX here to maximum value.
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
- // TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
}
sa.resetTargets();
diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java
index fb27b86a4d6..a8f46f2a180 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TapAiBase.java
@@ -5,6 +5,7 @@ import java.util.List;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
@@ -109,7 +110,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
* @return a boolean.
*/
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) {
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Game game = ai.getGame();
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
tapList = CardLists.getTargetableCards(tapList, sa);
diff --git a/forge-ai/src/main/java/forge/ai/ability/TwoPilesAi.java b/forge-ai/src/main/java/forge/ai/ability/TwoPilesAi.java
index b871171de7b..d5b687f0774 100644
--- a/forge-ai/src/main/java/forge/ai/ability/TwoPilesAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/TwoPilesAi.java
@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List;
+import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -28,7 +29,7 @@ public class TwoPilesAi extends SpellAbilityAi {
valid = sa.getParam("ValidCards");
}
- final Player opp = ai.getWeakestOpponent();
+ final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java b/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java
index 15b2c12a6d0..1e017a982d8 100644
--- a/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/UnattachAllAi.java
@@ -56,7 +56,6 @@ public class UnattachAllAi extends SpellAbilityAi {
@Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card card = sa.getHostCard();
- final Player opp = ai.getWeakestOpponent();
// Check if there are any valid targets
List targets = new ArrayList<>();
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -66,8 +65,8 @@ public class UnattachAllAi extends SpellAbilityAi {
if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
Card newTarget = (Card) targets.get(0);
- //don't equip human creatures
- if (newTarget.getController().equals(opp)) {
+ //don't equip opponent creatures
+ if (!newTarget.getController().equals(ai)) {
return false;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java
index 00c4837c82b..47c443f59b8 100644
--- a/forge-ai/src/main/java/forge/ai/ability/UntapAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/UntapAi.java
@@ -6,6 +6,7 @@ import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
+import forge.ai.AiAttackController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard;
@@ -130,7 +131,8 @@ public class UntapAi extends SpellAbilityAi {
Player targetController = ai;
if (sa.isCurse()) {
- targetController = ai.getWeakestOpponent();
+ // TODO search through all opponents, may need to check if different controllers allowed
+ targetController = AiAttackController.choosePreferredDefenderPlayer(ai);
}
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);
@@ -149,8 +151,7 @@ public class UntapAi extends SpellAbilityAi {
}
CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED);
- // filter out enchantments and planeswalkers, their tapped state doesn't
- // matter.
+ // filter out enchantments and planeswalkers, their tapped state doesn't matter.
final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);
diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java
index 8a35eee03e2..9d4320867b1 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -1247,17 +1247,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
currentState.addTrigger(t);
return t;
}
- @Deprecated
- public final void removeTrigger(final Trigger t) {
- currentState.removeTrigger(t);
- }
- @Deprecated
- public final void removeTrigger(final Trigger t, final CardStateName state) {
- getState(state).removeTrigger(t);
- }
- public final void clearTriggersNew() {
- currentState.clearTriggers();
- }
public final boolean hasTrigger(final Trigger t) {
return currentState.hasTrigger(t);
diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java
index c83ce6d79db..7954026a69c 100644
--- a/forge-game/src/main/java/forge/game/player/Player.java
+++ b/forge-game/src/main/java/forge/game/player/Player.java
@@ -421,6 +421,9 @@ public class Player extends GameEntity implements Comparable {
public final Player getWeakestOpponent() {
return getOpponents().min(PlayerPredicates.compareByLife());
}
+ public final Player getStrongestOpponent() {
+ return getOpponents().max(PlayerPredicates.compareByLife());
+ }
public boolean isOpponentOf(Player other) {
return other != this && other != null && (other.teamNumber < 0 || other.teamNumber != teamNumber);
diff --git a/forge-gui/res/cardsfolder/g/goblin_artisans.txt b/forge-gui/res/cardsfolder/g/goblin_artisans.txt
index 342618a2a0c..e0f85b739a0 100644
--- a/forge-gui/res/cardsfolder/g/goblin_artisans.txt
+++ b/forge-gui/res/cardsfolder/g/goblin_artisans.txt
@@ -10,4 +10,5 @@ SVar:DBCleanup:DB$ Cleanup | ForgetDefined$ TriggeredCard
A:AB$ FlipACoin | Cost$ T | TgtZone$ Stack | TargetType$ Spell | ValidTgts$ Artifact.YouCtrl+IsRemembered | WinSubAbility$ DBDraw | LoseSubAbility$ DBCounter | TgtPrompt$ Select target Artifact spell | SpellDescription$ Flip a coin. If you win the flip, draw a card. If you lose the flip, counter target artifact spell you control that isn't the target of an ability from another creature named Goblin Artisans.
SVar:DBDraw:DB$ Draw | NumCards$ 1 | Defined$ You
SVar:DBCounter:DB$ Counter | Defined$ Targeted
+AI:RemoveDeck:All
Oracle:{T}: Flip a coin. If you win the flip, draw a card. If you lose the flip, counter target artifact spell you control that isn't the target of an ability from another creature named Goblin Artisans.
diff --git a/forge-gui/res/cardsfolder/h/hellion_eruption.txt b/forge-gui/res/cardsfolder/h/hellion_eruption.txt
index 1d81e61d60b..b055e17e158 100644
--- a/forge-gui/res/cardsfolder/h/hellion_eruption.txt
+++ b/forge-gui/res/cardsfolder/h/hellion_eruption.txt
@@ -1,9 +1,8 @@
Name:Hellion Eruption
ManaCost:5 R
Types:Sorcery
-A:SP$ SacrificeAll | Cost$ 5 R | ValidCards$ Creature.YouCtrl | RememberSacrificed$ True | SubAbility$ DBToken | SpellDescription$ Sacrifice all creatures you control, then create that many 4/4 red Hellion creature tokens.
+A:SP$ SacrificeAll | Cost$ 5 R | ValidCards$ Creature.YouCtrl | RememberSacrificed$ True | SubAbility$ DBToken | AILogic$ HellionEruption | SpellDescription$ Sacrifice all creatures you control, then create that many 4/4 red Hellion creature tokens.
SVar:DBToken:DB$Token | TokenAmount$ X | TokenScript$ r_4_4_hellion | TokenOwner$ You | LegacyImage$ r 4 4 hellion roe
SVar:X:Remembered$Amount
-AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/hellion_eruption.jpg
Oracle:Sacrifice all creatures you control, then create that many 4/4 red Hellion creature tokens.