Merge branch 'AIopponent' into 'master'

AI Multiplayer improvements

See merge request core-developers/forge!4239
This commit is contained in:
Michael Kamensky
2021-03-26 10:58:22 +00:00
40 changed files with 287 additions and 358 deletions

View File

@@ -91,7 +91,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, boolean nextTurn) { public AiAttackController(final Player ai, boolean nextTurn) {
this.ai = ai; this.ai = ai;
this.defendingOpponent = choosePreferredDefenderPlayer(); this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(this.defendingOpponent); this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay(); this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>(); this.attackers = new ArrayList<>();
@@ -107,7 +107,7 @@ public class AiAttackController {
public AiAttackController(final Player ai, Card attacker) { public AiAttackController(final Player ai, Card attacker) {
this.ai = ai; this.ai = ai;
this.defendingOpponent = choosePreferredDefenderPlayer(); this.defendingOpponent = choosePreferredDefenderPlayer(ai);
this.oppList = getOpponentCreatures(this.defendingOpponent); this.oppList = getOpponentCreatures(this.defendingOpponent);
this.myList = ai.getCreaturesInPlay(); this.myList = ai.getCreaturesInPlay();
this.attackers = new ArrayList<>(); this.attackers = new ArrayList<>();
@@ -156,13 +156,12 @@ public class AiAttackController {
} }
/** Choose opponent for AI to attack here. Expand as necessary. */ /** Choose opponent for AI to attack here. Expand as necessary. */
private Player choosePreferredDefenderPlayer() { public static Player choosePreferredDefenderPlayer(Player ai) {
Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range
if (defender.getLife() < 8) { //Concentrate on opponent within easy kill range if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players
return defender; // TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked
} else { //Otherwise choose a random opponent to ensure no ganging up on players return ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
defender = ai.getOpponents().get(MyRandom.getRandom().nextInt(ai.getOpponents().size()));
} }
return defender; return defender;
} }
@@ -624,7 +623,7 @@ public class AiAttackController {
int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage; int totalCombatDamage = ComputerUtilCombat.sumDamageIfUnblocked(unblockedAttackers, opp) + trampleDamage;
int totalPoisonDamage = ComputerUtilCombat.sumPoisonIfUnblocked(unblockedAttackers, opp); 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)) { && !((opp.cantLoseForZeroOrLessLife() || ai.cantWin()) && opp.getLife() < 1)) {
return true; return true;
} }
@@ -919,7 +918,7 @@ public class AiAttackController {
// find the potential damage ratio the AI can cause // find the potential damage ratio the AI can cause
double humanLifeToDamageRatio = 1000000; double humanLifeToDamageRatio = 1000000;
if (candidateUnblockedDamage > 0) { 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 // determine if the ai outnumbers the player

View File

@@ -188,7 +188,6 @@ public class ComputerUtil {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
// Play higher costing spells first? // Play higher costing spells first?
final Cost cost = sa.getPayCosts(); final Cost cost = sa.getPayCosts();
@@ -213,7 +212,8 @@ public class ComputerUtil {
if (unless != null && !unless.endsWith(">")) { if (unless != null && !unless.endsWith(">")) {
final int amount = AbilityUtils.calculateAmount(source, unless, sa); 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 the Unless isn't enough, this should be less likely to be used
if (amount > usableManaSources) { if (amount > usableManaSources) {
@@ -1068,9 +1068,6 @@ public class ComputerUtil {
return true; return true;
} }
} }
if (card.isEquipment() && buffedcard.isCreature() && CombatUtil.canAttack(buffedcard, ComputerUtil.getOpponentFor(ai))) {
return true;
}
if (card.isCreature()) { if (card.isCreature()) {
if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) { if (buffedcard.hasKeyword(Keyword.SOULBOND) && !buffedcard.isPaired()) {
return true; return true;
@@ -1093,8 +1090,8 @@ public class ComputerUtil {
} // BuffedBy } // BuffedBy
// get all cards the human controls with AntiBuffedBy // there's a good chance AI will attack weak target
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield); final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
for (Card buffedcard : antibuffed) { for (Card buffedcard : antibuffed) {
if (buffedcard.hasSVar("AntiBuffedBy")) { if (buffedcard.hasSVar("AntiBuffedBy")) {
final String buffedby = buffedcard.getSVar("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 * @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) { public static boolean shouldCastLessThanMax(final Player ai, final Card source) {
boolean ret = true; if (source.getXManaCostPaid() > 0) {
if (source.getManaCost().countX() > 0) { // If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by payment resources available.
// If TargetMax is MaxTgts (i.e., an "X" cost), this is fine because AI is limited by mana available. return true;
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;
}
} }
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 // there's a good chance AI will attack weak target
final CardCollectionView antibuffed = ComputerUtil.getOpponentFor(ai).getCardsIn(ZoneType.Battlefield); final CardCollectionView antibuffed = ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield);
for (Card buffedcard : antibuffed) { for (Card buffedcard : antibuffed) {
if (buffedcard.hasSVar("AntiBuffedBy")) { if (buffedcard.hasSVar("AntiBuffedBy")) {
final String buffedby = buffedcard.getSVar("AntiBuffedBy"); final String buffedby = buffedcard.getSVar("AntiBuffedBy");
@@ -1463,7 +1449,7 @@ public class ComputerUtil {
return false; return false;
} }
public static int possibleNonCombatDamage(Player ai) { public static int possibleNonCombatDamage(Player ai, Player enemy) {
int damage = 0; int damage = 0;
final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield)); final CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
all.addAll(ai.getCardsActivableInExternalZones(true)); all.addAll(ai.getCardsActivableInExternalZones(true));
@@ -1483,7 +1469,6 @@ public class ComputerUtil {
if (tgt == null) { if (tgt == null) {
continue; continue;
} }
final Player enemy = ComputerUtil.getOpponentFor(ai);
if (!sa.canTarget(enemy)) { if (!sa.canTarget(enemy)) {
continue; continue;
} }
@@ -2346,7 +2331,7 @@ public class ComputerUtil {
} }
} }
else if (logic.equals("ChosenLandwalk")) { else if (logic.equals("ChosenLandwalk")) {
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) { for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
for (String t : c.getType()) { for (String t : c.getType()) {
if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) { if (!invalidTypes.contains(t) && CardType.isABasicLandType(t)) {
chosen = t; chosen = t;
@@ -2364,7 +2349,7 @@ public class ComputerUtil {
else if (kindOfType.equals("Land")) { else if (kindOfType.equals("Land")) {
if (logic != null) { if (logic != null) {
if (logic.equals("ChosenLandwalk")) { if (logic.equals("ChosenLandwalk")) {
for (Card c : ComputerUtil.getOpponentFor(ai).getLandsInPlay()) { for (Card c : AiAttackController.choosePreferredDefenderPlayer(ai).getLandsInPlay()) {
for (String t : c.getType().getLandTypes()) { for (String t : c.getType().getLandTypes()) {
if (!invalidTypes.contains(t)) { if (!invalidTypes.contains(t)) {
chosen = t; chosen = t;
@@ -2399,23 +2384,26 @@ public class ComputerUtil {
case "Torture": case "Torture":
return "Torture"; return "Torture";
case "GraceOrCondemnation": case "GraceOrCondemnation":
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace" List<ZoneType> graceZones = new ArrayList<ZoneType>();
: "Condemnation"; 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": case "CarnageOrHomage":
CardCollection cardsInPlay = CardLists CardCollection cardsInPlay = CardLists.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
.getNotType(sa.getHostCard().getGame().getCardsIn(ZoneType.Battlefield), "Land");
CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents()); CardCollection humanlist = CardLists.filterControlledBy(cardsInPlay, ai.getOpponents());
CardCollection computerlist = CardLists.filterControlledBy(cardsInPlay, ai); CardCollection computerlist = ai.getCreaturesInPlay();
return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard return (ComputerUtilCard.evaluatePermanentList(computerlist) + 3) < ComputerUtilCard.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
.evaluatePermanentList(humanlist) ? "Carnage" : "Homage";
case "Judgment": case "Judgment":
if (votes.isEmpty()) { if (votes.isEmpty()) {
CardCollection list = new CardCollection(); CardCollection list = new CardCollection();
for (Object o : options) { for (Object o : options) {
if (o instanceof Card) { if (o instanceof Card) {
list.add((Card) o); list.add((Card) o);
}
} }
}
return ComputerUtilCard.getBestAI(list); return ComputerUtilCard.getBestAI(list);
} else { } else {
return Iterables.getFirst(votes.keySet(), null); return Iterables.getFirst(votes.keySet(), null);
@@ -2934,23 +2922,6 @@ public class ComputerUtil {
return true; 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) { public static int countUsefulCreatures(Player p) {
CardCollection creats = p.getCreaturesInPlay(); CardCollection creats = p.getCreaturesInPlay();
int count = 0; int count = 0;
@@ -3033,31 +3004,32 @@ public class ComputerUtil {
// call this to determine if it's safe to use a life payment spell // 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. // or trigger "emergency" strategies such as holding mana for Spike Weaver of Counterspell.
public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) { public static boolean aiLifeInDanger(Player ai, boolean serious, int payment) {
Player opponent = ComputerUtil.getOpponentFor(ai); for (Player opponent: ai.getOpponents()) {
// test whether the human can kill the ai next turn // test whether the human can kill the ai next turn
Combat combat = new Combat(opponent); Combat combat = new Combat(opponent);
boolean containsAttacker = false; boolean containsAttacker = false;
for (Card att : opponent.getCreaturesInPlay()) { for (Card att : opponent.getCreaturesInPlay()) {
if (ComputerUtilCombat.canAttackNextTurn(att, ai)) { if (ComputerUtilCombat.canAttackNextTurn(att, ai)) {
combat.addAttacker(att, ai); combat.addAttacker(att, ai);
containsAttacker = true; containsAttacker = true;
}
} }
} if (!containsAttacker) {
if (!containsAttacker) { return false;
return false; }
} AiBlockController block = new AiBlockController(ai);
AiBlockController block = new AiBlockController(ai); block.assignBlockersForCombat(combat);
block.assignBlockersForCombat(combat);
// TODO predict other, noncombat sources of damage and add them to the "payment" variable. // 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 // 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 added, might need a parameter to define whether we want to check all threats or combat threats.
if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) { if ((serious) && (ComputerUtilCombat.lifeInSeriousDanger(ai, combat, payment))) {
return true; return true;
} }
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) { if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
return true; return true;
}
} }
return false; return false;

View File

@@ -550,7 +550,7 @@ public class ComputerUtilCard {
*/ */
public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) { public static CardCollectionView getLikelyBlockers(final Player ai, final CardCollectionView blockers) {
AiBlockController aiBlk = new AiBlockController(ai); AiBlockController aiBlk = new AiBlockController(ai);
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
Combat combat = new Combat(opp); Combat combat = new Combat(opp);
//Use actual attackers if available, else consider all possible attackers //Use actual attackers if available, else consider all possible attackers
Combat currentCombat = ai.getGame().getCombat(); Combat currentCombat = ai.getGame().getCombat();

View File

@@ -97,34 +97,39 @@ public class ComputerUtilCombat {
* canAttackNextTurn. * canAttackNextTurn.
* </p> * </p>
* *
* @param atacker * @param attacker
* a {@link forge.game.card.Card} object. * a {@link forge.game.card.Card} object.
* @param defender * @param defender
* the defending {@link GameEntity}. * the defending {@link GameEntity}.
* @return a boolean. * @return a boolean.
*/ */
public static boolean canAttackNextTurn(final Card atacker, final GameEntity defender) { public static boolean canAttackNextTurn(final Card attacker, final GameEntity defender) {
if (!atacker.isCreature()) { if (!attacker.isCreature()) {
return false; return false;
} }
if (!CombatUtil.canAttackNextTurn(atacker, defender)) { if (!CombatUtil.canAttackNextTurn(attacker, defender)) {
return false; return false;
} }
for (final KeywordInterface inst : atacker.getKeywords()) { for (final KeywordInterface inst : attacker.getKeywords()) {
final String keyword = inst.getOriginal(); final String keyword = inst.getOriginal();
if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) { if (keyword.startsWith("CARDNAME attacks specific player each combat if able")) {
final String defined = keyword.split(":")[1]; 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)) { if (!defender.equals(player)) {
return false; 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 // The creature won't untap next turn
return !atacker.isTapped() || Untap.canUntap(atacker); return !attacker.isTapped() || Untap.canUntap(attacker);
} // canAttackNextTurn(Card, GameEntity) }
/** /**
* <p> * <p>

View File

@@ -22,7 +22,6 @@ public class ActivateAbilityAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Player opp = ai.getWeakestOpponent(); final Player opp = ai.getWeakestOpponent();
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card")); List<Card> list = CardLists.getType(opp.getCardsIn(ZoneType.Battlefield), sa.getParamOrDefault("Type", "Card"));
if (list.isEmpty()) { if (list.isEmpty()) {
@@ -40,6 +39,7 @@ public class ActivateAbilityAi extends SpellAbilityAi {
sa.getTargets().add(opp); sa.getTargets().add(opp);
} }
boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn());
return randomReturn; return randomReturn;
} }
@@ -56,7 +56,8 @@ public class ActivateAbilityAi extends SpellAbilityAi {
} else { } else {
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa); final List<Player> 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 { } else {

View File

@@ -13,18 +13,21 @@ public class BalanceAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
String logic = sa.getParam("AILogic"); String logic = sa.getParam("AILogic");
int diff = 0; int diff = 0;
// TODO Add support for multiplayer logic Player opp = aiPlayer.getWeakestOpponent();
final Player opp = aiPlayer.getWeakestOpponent();
final CardCollectionView humPerms = opp.getCardsIn(ZoneType.Battlefield);
final CardCollectionView compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield); 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)) { 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() - diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() -
CardLists.filter(compPerms, 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()); CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size());
} }
else if ("BalancePermanents".equals(logic)) { else if ("BalancePermanents".equals(logic)) {

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
@@ -24,7 +25,7 @@ public class BidLifeAi extends SpellAbilityAi {
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
if (tgt.canTgtCreature()) { if (tgt.canTgtCreature()) {
List<Card> list = CardLists.getTargetableCards(aiPlayer.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), sa); List<Card> list = CardLists.getTargetableCards(AiAttackController.choosePreferredDefenderPlayer(aiPlayer).getCardsIn(ZoneType.Battlefield), sa);
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa); list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
if (list.isEmpty()) { if (list.isEmpty()) {
return false; return false;

View File

@@ -15,6 +15,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.ai.AiAttackController;
import forge.ai.AiBlockController; import forge.ai.AiBlockController;
import forge.ai.AiCardMemory; import forge.ai.AiCardMemory;
import forge.ai.AiController; import forge.ai.AiController;
@@ -263,7 +264,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa);
ZoneType origin = null; ZoneType origin = null;
final Player opponent = ai.getWeakestOpponent(); final Player opponent = AiAttackController.choosePreferredDefenderPlayer(ai);
boolean activateForCost = ComputerUtil.activateForCost(sa, ai); boolean activateForCost = ComputerUtil.activateForCost(sa, ai);
if (sa.hasParam("Origin")) { 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 // if putting cards from hand to library and parent is drawing cards
// make sure this will actually do something: // make sure this will actually do something:
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = aiPlayer.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (tgt != null && tgt.canTgtPlayer()) { if (tgt != null && tgt.canTgtPlayer()) {
boolean isCurse = sa.isCurse(); boolean isCurse = sa.isCurse();
if (isCurse && sa.canTarget(opp)) { if (isCurse && sa.canTarget(opp)) {
@@ -530,7 +531,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
Iterable<Player> pDefined; Iterable<Player> pDefined;
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if ((tgt != null) && tgt.canTgtPlayer()) { if ((tgt != null) && tgt.canTgtPlayer()) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.isCurse()) { if (sa.isCurse()) {
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.getTargets().add(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? // TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
sa.setXManaCostPaid(xPay); sa.setXManaCostPaid(xPay);
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
} }
CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa); CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa);
@@ -913,9 +913,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (sa.isSpell()) { if (sa.isSpell()) {
list.remove(source); // spells can't target their own source, because it's actually in the stack zone 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")) { if (sa.hasParam("AttachedTo")) {
//System.out.println("isPreferredTarget att " + list);
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
@@ -927,7 +925,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false; return false;
} }
}); });
//System.out.println("isPreferredTarget ok " + list);
} }
if (list.size() < sa.getMinTargets()) { if (list.size() < sa.getMinTargets()) {
@@ -1482,9 +1479,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
if ("DeathgorgeScavenger".equals(logic)) { if ("DeathgorgeScavenger".equals(logic)) {
return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa); return SpecialCardAi.DeathgorgeScavenger.consider(ai, sa);
} else if ("ExtraplanarLens".equals(logic)) { }
if ("ExtraplanarLens".equals(logic)) {
return SpecialCardAi.ExtraplanarLens.consider(ai, sa); return SpecialCardAi.ExtraplanarLens.consider(ai, sa);
} else if ("ExileCombatThreat".equals(logic)) { }
if ("ExileCombatThreat".equals(logic)) {
return doExileCombatThreatLogic(ai, sa); return doExileCombatThreatLogic(ai, sa);
} }
@@ -1984,11 +1983,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
toPay = AbilityUtils.calculateAmount(source, unlessCost, sa); toPay = AbilityUtils.calculateAmount(source, unlessCost, sa);
} }
if (toPay == 0) { if (toPay == 0 || toPay <= usableManaSources) {
canBeSaved.add(potentialTgt);
}
if (toPay <= usableManaSources) {
canBeSaved.add(potentialTgt); canBeSaved.add(potentialTgt);
} }

View File

@@ -9,6 +9,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
@@ -114,7 +115,7 @@ public class ChooseCardAi extends SpellAbilityAi {
return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty(); return !CardLists.getValidCards(choices, "Card.nonLand", host.getController(), host, sa).isEmpty();
} else if (aiLogic.equals("Duneblast")) { } else if (aiLogic.equals("Duneblast")) {
CardCollection aiCreatures = ai.getCreaturesInPlay(); CardCollection aiCreatures = ai.getCreaturesInPlay();
CardCollection oppCreatures = ai.getWeakestOpponent().getCreaturesInPlay(); CardCollection oppCreatures = AiAttackController.choosePreferredDefenderPlayer(ai).getCreaturesInPlay();
aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE); aiCreatures = CardLists.getNotKeyword(aiCreatures, Keyword.INDESTRUCTIBLE);
oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE); oppCreatures = CardLists.getNotKeyword(oppCreatures, Keyword.INDESTRUCTIBLE);

View File

@@ -7,6 +7,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.StaticData; import forge.StaticData;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpecialCardAi; import forge.ai.SpecialCardAi;
@@ -44,7 +45,7 @@ public class ChooseCardNameAi extends SpellAbilityAi {
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
if (tgt.canOnlyTgtOpponent()) { if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(ai.getWeakestOpponent()); sa.getTargets().add(AiAttackController.choosePreferredDefenderPlayer(ai));
} else { } else {
sa.getTargets().add(ai); sa.getTargets().add(ai);
} }

View File

@@ -1,5 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -16,7 +17,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
TargetRestrictions tgt = sa.getTargetRestrictions(); TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
Player opp = aiPlayer.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);
} else { } else {

View File

@@ -1,5 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -16,7 +17,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
TargetRestrictions tgt = sa.getTargetRestrictions(); TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
Player opp = aiPlayer.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);
} else { } else {

View File

@@ -8,6 +8,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
@@ -54,7 +55,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);
} else { } else {

View File

@@ -3,6 +3,7 @@ package forge.ai.ability;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -29,7 +30,7 @@ public class ControlExchangeAi extends SpellAbilityAi {
sa.resetTargets(); sa.resetTargets();
CardCollection list = 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 // AI won't try to grab cards that are filtered out of AI decks on
// purpose // purpose
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {

View File

@@ -8,6 +8,7 @@ import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
@@ -26,9 +27,6 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public class DebuffAi extends SpellAbilityAi { public class DebuffAi extends SpellAbilityAi {
// *************************************************************************
// ***************************** Debuff ************************************
// *************************************************************************
@Override @Override
protected boolean canPlayAI(final Player ai, final SpellAbility sa) { 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)) { while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
Card t = null; Card t = null;
// boolean goodt = false;
if (list.isEmpty()) { if (list.isEmpty()) {
if ((sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) || (sa.getTargets().size() == 0)) { 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. * @return a CardCollection.
*/ */
private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) { private CardCollection getCurseCreatures(final Player ai, final SpellAbility sa, final List<String> kws) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa); CardCollection list = CardLists.getTargetableCards(opp.getCreaturesInPlay(), sa);
if (!list.isEmpty()) { if (!list.isEmpty()) {
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return c.hasAnyKeyword(kws); // don't add duplicate negative return c.hasAnyKeyword(kws); // don't add duplicate negative keywords
// keywords
} }
}); });
} }
return list; return list;
} // getCurseCreatures() }
/** /**
* <p> * <p>
@@ -216,7 +212,7 @@ public class DebuffAi extends SpellAbilityAi {
list.remove(c); 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 CardCollection forced = CardLists.filterControlledBy(list, ai);
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
@@ -242,8 +238,7 @@ public class DebuffAi extends SpellAbilityAi {
break; break;
} }
// TODO - if forced targeting, just pick something without the given // TODO - if forced targeting, just pick something without the given keyword
// keyword
Card c; Card c;
if (CardLists.getNotType(forced, "Creature").size() == 0) { if (CardLists.getNotType(forced, "Creature").size() == 0) {
c = ComputerUtilCard.getWorstCreatureAI(forced); c = ComputerUtilCard.getWorstCreatureAI(forced);

View File

@@ -70,12 +70,12 @@ public class DestroyAllAi extends SpellAbilityAi {
return doMassRemovalLogic(ai, sa); 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 Card source = sa.getHostCard();
final String logic = sa.getParamOrDefault("AILogic", ""); 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")) { 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 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)); valid = valid.replace("X", Integer.toString(xPay));
} }
CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), // TODO should probably sort results when targeted to use on biggest threat instead of first match
valid.split(","), source.getController(), source, sa); for (Player opponent: ai.getOpponents()) {
CardCollection ailist = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), valid.split(","), CardCollection opplist = CardLists.getValidCards(opponent.getCardsIn(ZoneType.Battlefield), valid.split(","), source.getController(), source, sa);
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;
}
if (sa.usesTargeting()) { opplist = CardLists.filter(opplist, predicate);
sa.resetTargets(); ailist = CardLists.filter(ailist, predicate);
if (sa.canTarget(opponent)) { if (opplist.isEmpty()) {
sa.getTargets().add(opponent);
ailist.clear();
} else {
return false; return false;
} }
}
// Special handling for Raiding Party if (sa.usesTargeting()) {
if (logic.equals("RaidingParty")) { sa.resetTargets();
int numAiCanSave = Math.min(CardLists.filter(ai.getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, ailist.size()); if (sa.canTarget(opponent)) {
int numOppsCanSave = Math.min(CardLists.filter(ai.getOpponents().getCreaturesInPlay(), Predicates.and(CardPredicates.isColor(MagicColor.WHITE), CardPredicates.Presets.UNTAPPED)).size() * 2, opplist.size()); sa.getTargets().add(opponent);
ailist.clear();
return numOppsCanSave < opplist.size() && (ailist.size() - numAiCanSave < opplist.size() - numOppsCanSave); } else {
}
// 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) {
return false; 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; return false;
} }
} // otherwise evaluate both lists by CMC and pass only if human permanents are more valuable return true;
else if ((ComputerUtilCard.evaluatePermanentList(ailist) + 3) >= ComputerUtilCard.evaluatePermanentList(opplist)) {
return false;
} }
return true; return false;
} }
}
}

View File

@@ -5,6 +5,7 @@ import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
@@ -32,7 +33,7 @@ public class DigAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame(); final Game game = ai.getGame();
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
Player libraryOwner = ai; Player libraryOwner = ai;
@@ -120,7 +121,7 @@ public class DigAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final SpellAbility root = sa.getRootAbility(); final SpellAbility root = sa.getRootAbility();
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.resetTargets(); sa.resetTargets();
if (mandatory && sa.canTarget(opp)) { if (mandatory && sa.canTarget(opp)) {

View File

@@ -1,5 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.Game; import forge.game.Game;
@@ -19,7 +20,7 @@ public class DigMultipleAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Game game = ai.getGame(); final Game game = ai.getGame();
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final Card host = sa.getHostCard(); final Card host = sa.getHostCard();
Player libraryOwner = ai; Player libraryOwner = ai;
@@ -77,7 +78,7 @@ public class DigMultipleAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.resetTargets(); sa.resetTargets();
if (mandatory && sa.canTarget(opp)) { if (mandatory && sa.canTarget(opp)) {

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
@@ -31,10 +32,8 @@ public class DigUntilAi extends SpellAbilityAi {
chance = 1; chance = 1;
} }
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
Player libraryOwner = ai; Player libraryOwner = ai;
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if ("DontMillSelf".equals(logic)) { if ("DontMillSelf".equals(logic)) {
// A card that digs for specific things and puts everything revealed before it into graveyard // 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; return false;
} }
final boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(chance, sa.getActivationsThisTurn() + 1);
return randomReturn; return randomReturn;
} }
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
sa.resetTargets(); sa.resetTargets();
if (sa.isCurse()) { if (sa.isCurse()) {

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
@@ -60,11 +61,9 @@ public class DiscardAi extends SpellAbilityAi {
if (players.get(0) == ai) { if (players.get(0) == ai) {
// the ai should only be using something like this if he has // the ai should only be using something like this if he has
// few cards in hand, // few cards in hand,
// cards like this better have a good drawback to be in the // cards like this better have a good drawback to be in the AIs deck
// AIs deck
} else { } else {
// defined to the human, so that's fine as long the human // defined to the human, so that's fine as long the human has cards
// has cards
if (!humanHasHand) { if (!humanHasHand) {
return false; return false;
} }
@@ -170,7 +169,7 @@ public class DiscardAi extends SpellAbilityAi {
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (!discardTargetAI(ai, sa)) { if (!discardTargetAI(ai, sa)) {
if (mandatory && sa.canTarget(opp)) { if (mandatory && sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);

View File

@@ -23,8 +23,7 @@ public class DrainManaAi extends SpellAbilityAi {
if (tgt == null) { if (tgt == null) {
// assume we are looking to tap human's stuff // assume we are looking to tap human's stuff
// TODO - check for things with untap abilities, and don't tap // TODO - check for things with untap abilities, and don't tap those.
// those.
final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa); final List<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
if (!defined.contains(opp)) { if (!defined.contains(opp)) {

View File

@@ -29,11 +29,10 @@ public class GameLossAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
// Phage the Untouchable // Phage the Untouchable
// (Final Fortune would need to attach it's delayed trigger to a // (Final Fortune would need to attach it's delayed trigger to a
// specific turn, which can't be done yet) // specific turn, which can't be done yet)
Player opp = ai.getWeakestOpponent(); Player opp = ai.getGame().getCombat().getDefenderPlayerByAttacker(sa.getHostCard());
if (!mandatory && opp.cantLose()) { if (!mandatory && opp.cantLose()) {
return false; return false;

View File

@@ -1,5 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -19,7 +20,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
final int myLife = aiPlayer.getLife(); final int myLife = aiPlayer.getLife();
Player opponent = aiPlayer.getWeakestOpponent(); Player opponent = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
final int hLife = opponent.getLife(); final int hLife = opponent.getLife();
if (!aiPlayer.canGainLife()) { if (!aiPlayer.canGainLife()) {
@@ -75,7 +76,7 @@ public class LifeExchangeAi extends SpellAbilityAi {
final boolean mandatory) { final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) { if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {

View File

@@ -1,5 +1,6 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.AiProps; import forge.ai.AiProps;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
@@ -154,7 +155,7 @@ public class LifeExchangeVariantAi extends SpellAbilityAi {
final boolean mandatory) { final boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
Player opp = ai.getWeakestOpponent(); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) { if (sa.canTarget(opp) && (mandatory || ai.getLife() < opp.getLife())) {

View File

@@ -17,7 +17,7 @@ public class LifeSetAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final int myLife = ai.getLife(); final int myLife = ai.getLife();
final Player opponent = ai.getWeakestOpponent(); final Player opponent = ai.getStrongestOpponent();
final int hlife = opponent.getLife(); final int hlife = opponent.getLife();
final String amountStr = sa.getParam("LifeAmount"); final String amountStr = sa.getParam("LifeAmount");
@@ -36,8 +36,7 @@ public class LifeSetAi extends SpellAbilityAi {
return false; return false;
} }
// TODO handle proper calculation of X values based on Cost and what // TODO handle proper calculation of X values based on Cost and what would be paid
// would be paid
int amount; int amount;
// we shouldn't have to worry too much about PayX for SetLife // we shouldn't have to worry too much about PayX for SetLife
if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) { if (amountStr.equals("X") && sa.getSVar(amountStr).equals("Count$xPaid")) {
@@ -58,11 +57,9 @@ public class LifeSetAi extends SpellAbilityAi {
if (tgt.canOnlyTgtOpponent()) { if (tgt.canOnlyTgtOpponent()) {
sa.getTargets().add(opponent); sa.getTargets().add(opponent);
// if we can only target the human, and the Human's life // if we can only target the human, and the Human's life
// would // would go up, don't play it.
// go up, don't play it.
// possibly add a combo here for Magister Sphinx and // possibly add a combo here for Magister Sphinx and
// Higedetsu's // Higedetsu's (sp?) Second Rite
// (sp?) Second Rite
if ((amount > hlife) || !opponent.canLoseLife()) { if ((amount > hlife) || !opponent.canLoseLife()) {
return false; return false;
} }
@@ -81,8 +78,7 @@ public class LifeSetAi extends SpellAbilityAi {
if (sa.getParam("Defined").equals("Player")) { if (sa.getParam("Defined").equals("Player")) {
if (amount == 0) { if (amount == 0) {
return false; return false;
} else if (myLife > amount) { // will decrease computer's } else if (myLife > amount) { // will decrease computer's life
// life
if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) { if ((myLife < 5) || ((myLife - amount) > (hlife - amount))) {
return false; return false;
} }
@@ -104,7 +100,7 @@ public class LifeSetAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final int myLife = ai.getLife(); final int myLife = ai.getLife();
final Player opponent = ai.getWeakestOpponent(); final Player opponent = ai.getStrongestOpponent();
final int hlife = opponent.getLife(); final int hlife = opponent.getLife();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); 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 gaining life, target self.
// if the Target is modifying how much life is gained, this needs to // if the Target is modifying how much life is gained, this needs to be handled better
// be handled better
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();

View File

@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
sa.resetTargets(); sa.resetTargets();
List<Card> list = List<Card> list =
CardLists.getValidCards(ai.getWeakestOpponent().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, sa.getHostCard(), sa); 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 // AI won't try to grab cards that are filtered out of AI decks on purpose
// purpose
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { 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); CardLists.sortByPowerAsc(list);

View File

@@ -6,6 +6,7 @@ import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat; import forge.ai.ComputerUtilCombat;
@@ -201,7 +202,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
final Game game = ai.getGame(); final Game game = ai.getGame();
final Combat combat = game.getCombat(); final Combat combat = game.getCombat();
final PhaseHandler ph = game.getPhaseHandler(); final PhaseHandler ph = game.getPhaseHandler();
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final int newPower = card.getNetCombatDamage() + attack; final int newPower = card.getNetCombatDamage() + attack;
//int defense = getNumDefense(sa); //int defense = getNumDefense(sa);
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) { if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {

View File

@@ -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<String> keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList<>();
final PhaseType phase = game.getPhaseHandler().getPhase();
if (ComputerUtil.preventRunAwayActivations(sa)) { if (ComputerUtil.preventRunAwayActivations(sa)) {
return false; return false;
} }
@@ -63,16 +57,10 @@ public class PumpAllAi extends PumpAiBase {
return false; 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 TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getStrongestOpponent();
if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) { if (tgt != null && sa.canTarget(opp) && sa.hasParam("IsCurse")) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(opp); sa.getTargets().add(opp);
@@ -85,6 +73,18 @@ public class PumpAllAi extends PumpAiBase {
return true; 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<String> 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()) { if (!game.getStack().isEmpty() && !sa.isCurse()) {
return pumpAgainstRemoval(ai, sa, comp); return pumpAgainstRemoval(ai, sa, comp);
} }
@@ -139,8 +139,7 @@ public class PumpAllAi extends PumpAiBase {
return true; return true;
} }
// evaluate both lists and pass only if human creatures are more // evaluate both lists and pass only if human creatures are more valuable
// valuable
return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human); return (ComputerUtilCard.evaluateCreatureList(comp) + 200) < ComputerUtilCard.evaluateCreatureList(human);
} // end Curse } // end Curse

View File

@@ -1,6 +1,7 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiAttackController;
import forge.ai.AiController; import forge.ai.AiController;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.PlayerControllerAi; import forge.ai.PlayerControllerAi;
@@ -14,7 +15,7 @@ public class RepeatAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
if (!opp.canBeTargetedBy(sa)) { if (!opp.canBeTargetedBy(sa)) {
@@ -44,9 +45,8 @@ public class RepeatAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
if (sa.usesTargeting()) { if (sa.usesTargeting()) {
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.resetTargets(); sa.resetTargets();
sa.getTargets().add(opp); sa.getTargets().add(opp);

View File

@@ -20,9 +20,6 @@ import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
public class SacrificeAi extends SpellAbilityAi { public class SacrificeAi extends SpellAbilityAi {
// **************************************************************
// *************************** Sacrifice ***********************
// **************************************************************
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { 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: // Improve AI for triggers. If source is a creature with:
// When ETB, sacrifice a creature. Check to see if the AI has something // When ETB, sacrifice a creature. Check to see if the AI has something to sacrifice
// to sacrifice
// Eventually, we can call the trigger of ETB abilities with not // Eventually, we can call the trigger of ETB abilities with not
// mandatory as part of the checks to cast something // 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) { private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
final boolean destroy = sa.hasParam("Destroy"); final boolean destroy = sa.hasParam("Destroy");
Player opp = ai.getWeakestOpponent(); Player opp = ai.getStrongestOpponent();
if (tgt != null) { if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
@@ -109,8 +104,7 @@ public class SacrificeAi extends SpellAbilityAi {
sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount)); sa.setXManaCostPaid(Math.min(ComputerUtilCost.getMaxXValue(sa, ai), amount));
} }
final int half = (amount / 2) + (amount % 2); // Half of amount final int half = (amount / 2) + (amount % 2); // Half of amount rounded up
// rounded up
// If the Human has at least half rounded up of the amount to be // If the Human has at least half rounded up of the amount to be
// sacrificed, cast the spell // sacrificed, cast the spell
@@ -130,8 +124,7 @@ public class SacrificeAi extends SpellAbilityAi {
// If Sacrifice hits both players: // If Sacrifice hits both players:
// Only cast it if Human has the full amount of valid // Only cast it if Human has the full amount of valid
// Only cast it if AI doesn't have 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 // TODO: Cast if the type is favorable: my "worst" valid is worse than his "worst" valid
// worse than his "worst" valid
final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1"; final String num = sa.hasParam("Amount") ? sa.getParam("Amount") : "1";
int amount = AbilityUtils.calculateAmount(source, num, sa); int amount = AbilityUtils.calculateAmount(source, num, sa);

View File

@@ -2,17 +2,12 @@ package forge.ai.ability;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCost; import forge.ai.ComputerUtilCost;
import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardLists;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.TextUtil;
public class SacrificeAllAi extends SpellAbilityAi { public class SacrificeAllAi extends SpellAbilityAi {
@@ -22,22 +17,7 @@ public class SacrificeAllAi extends SpellAbilityAi {
// based on what the expected targets could be // based on what the expected targets could be
final Cost abCost = sa.getPayCosts(); final Cost abCost = sa.getPayCosts();
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
String valid = ""; final String logic = sa.getParamOrDefault("AILogic", "");
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);
if (abCost != null) { if (abCost != null) {
// AI currently disabled for some costs // AI currently disabled for some costs
@@ -45,30 +25,20 @@ public class SacrificeAllAi extends SpellAbilityAi {
return false; 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 // prevent run-away activations - first time will always return true
boolean chance = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); 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); return ((MyRandom.getRandom().nextFloat() < .9667) && chance);
} }

View File

@@ -66,7 +66,6 @@ public class TapAi extends TapAiBase {
// Set PayX here to maximum value. // Set PayX here to maximum value.
// TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore? // TODO need to set XManaCostPaid for targets, maybe doesn't need PayX anymore?
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai)); sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai));
// TODO since change of PayX. the shouldCastLessThanMax logic might be faulty
} }
sa.resetTargets(); sa.resetTargets();

View File

@@ -5,6 +5,7 @@ import java.util.List;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
@@ -109,7 +110,7 @@ public abstract class TapAiBase extends SpellAbilityAi {
* @return a boolean. * @return a boolean.
*/ */
protected boolean tapPrefTargeting(final Player ai, final Card source, final SpellAbility sa, final boolean mandatory) { 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(); final Game game = ai.getGame();
CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents()); CardCollection tapList = CardLists.filterControlledBy(game.getCardsIn(ZoneType.Battlefield), ai.getOpponents());
tapList = CardLists.getTargetableCards(tapList, sa); tapList = CardLists.getTargetableCards(tapList, sa);

View File

@@ -2,6 +2,7 @@ package forge.ai.ability;
import java.util.List; import java.util.List;
import forge.ai.AiAttackController;
import forge.ai.SpellAbilityAi; import forge.ai.SpellAbilityAi;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
@@ -28,7 +29,7 @@ public class TwoPilesAi extends SpellAbilityAi {
valid = sa.getParam("ValidCards"); valid = sa.getParam("ValidCards");
} }
final Player opp = ai.getWeakestOpponent(); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) { if (tgt != null) {

View File

@@ -56,7 +56,6 @@ public class UnattachAllAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
final Player opp = ai.getWeakestOpponent();
// Check if there are any valid targets // Check if there are any valid targets
List<GameObject> targets = new ArrayList<>(); List<GameObject> targets = new ArrayList<>();
final TargetRestrictions tgt = sa.getTargetRestrictions(); final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -66,8 +65,8 @@ public class UnattachAllAi extends SpellAbilityAi {
if (!mandatory && card.isEquipment() && !targets.isEmpty()) { if (!mandatory && card.isEquipment() && !targets.isEmpty()) {
Card newTarget = (Card) targets.get(0); Card newTarget = (Card) targets.get(0);
//don't equip human creatures //don't equip opponent creatures
if (newTarget.getController().equals(opp)) { if (!newTarget.getController().equals(ai)) {
return false; return false;
} }

View File

@@ -6,6 +6,7 @@ import java.util.Map;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import forge.ai.AiAttackController;
import forge.ai.ComputerUtil; import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilAbility; import forge.ai.ComputerUtilAbility;
import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCard;
@@ -130,7 +131,8 @@ public class UntapAi extends SpellAbilityAi {
Player targetController = ai; Player targetController = ai;
if (sa.isCurse()) { 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); 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); CardCollection untapList = targetUntapped ? list : CardLists.filter(list, Presets.TAPPED);
// filter out enchantments and planeswalkers, their tapped state doesn't // filter out enchantments and planeswalkers, their tapped state doesn't matter.
// matter.
final String[] tappablePermanents = {"Creature", "Land", "Artifact"}; final String[] tappablePermanents = {"Creature", "Land", "Artifact"};
untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa); untapList = CardLists.getValidCards(untapList, tappablePermanents, source.getController(), source, sa);

View File

@@ -1247,17 +1247,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
currentState.addTrigger(t); currentState.addTrigger(t);
return 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) { public final boolean hasTrigger(final Trigger t) {
return currentState.hasTrigger(t); return currentState.hasTrigger(t);

View File

@@ -421,6 +421,9 @@ public class Player extends GameEntity implements Comparable<Player> {
public final Player getWeakestOpponent() { public final Player getWeakestOpponent() {
return getOpponents().min(PlayerPredicates.compareByLife()); return getOpponents().min(PlayerPredicates.compareByLife());
} }
public final Player getStrongestOpponent() {
return getOpponents().max(PlayerPredicates.compareByLife());
}
public boolean isOpponentOf(Player other) { public boolean isOpponentOf(Player other) {
return other != this && other != null && (other.teamNumber < 0 || other.teamNumber != teamNumber); return other != this && other != null && (other.teamNumber < 0 || other.teamNumber != teamNumber);

View File

@@ -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. 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:DBDraw:DB$ Draw | NumCards$ 1 | Defined$ You
SVar:DBCounter:DB$ Counter | Defined$ Targeted 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. 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.

View File

@@ -1,9 +1,8 @@
Name:Hellion Eruption Name:Hellion Eruption
ManaCost:5 R ManaCost:5 R
Types:Sorcery 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:DBToken:DB$Token | TokenAmount$ X | TokenScript$ r_4_4_hellion | TokenOwner$ You | LegacyImage$ r 4 4 hellion roe
SVar:X:Remembered$Amount SVar:X:Remembered$Amount
AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/hellion_eruption.jpg 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. Oracle:Sacrifice all creatures you control, then create that many 4/4 red Hellion creature tokens.