mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 04:38:00 +00:00
Merge branch 'AIopponent' into 'master'
AI Multiplayer improvements See merge request core-developers/forge!4239
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
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;
|
||||
}
|
||||
if (aiLifeInDanger(ai, false, 0)) {
|
||||
// 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()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
AiBlockController aiBlock = new AiBlockController(ai);
|
||||
aiBlock.assignBlockersForCombat(combat);
|
||||
if (!ComputerUtilCombat.lifeInDanger(ai, combat)) {
|
||||
// Otherwise, return false. Do not play now.
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
// 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,15 +2384,18 @@ public class ComputerUtil {
|
||||
case "Torture":
|
||||
return "Torture";
|
||||
case "GraceOrCondemnation":
|
||||
return ai.getCreaturesInPlay().size() > ComputerUtil.getOpponentFor(ai).getCreaturesInPlay().size() ? "Grace"
|
||||
: "Condemnation";
|
||||
List<ZoneType> graceZones = new ArrayList<ZoneType>();
|
||||
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();
|
||||
@@ -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,7 +3004,7 @@ 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);
|
||||
for (Player opponent: ai.getOpponents()) {
|
||||
// test whether the human can kill the ai next turn
|
||||
Combat combat = new Combat(opponent);
|
||||
boolean containsAttacker = false;
|
||||
@@ -3059,6 +3030,7 @@ public class ComputerUtil {
|
||||
if ((!serious) && (ComputerUtilCombat.lifeInDanger(ai, combat, payment))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -97,34 +97,39 @@ public class ComputerUtilCombat {
|
||||
* canAttackNextTurn.
|
||||
* </p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
||||
@@ -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<Card> 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<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 {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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<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);
|
||||
if (list.isEmpty()) {
|
||||
return false;
|
||||
|
||||
@@ -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<Player> 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<Card>() {
|
||||
@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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Card>() {
|
||||
|
||||
@@ -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<String> 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<Card>() {
|
||||
@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()
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -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);
|
||||
|
||||
@@ -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,10 +93,10 @@ 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);
|
||||
// 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);
|
||||
|
||||
opplist = CardLists.filter(opplist, predicate);
|
||||
ailist = CardLists.filter(ailist, predicate);
|
||||
@@ -135,8 +135,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if only creatures are affected evaluate both lists and pass only if
|
||||
// human creatures are more valuable
|
||||
// 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;
|
||||
@@ -167,7 +166,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
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()) {
|
||||
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
|
||||
@@ -188,4 +187,7 @@ public class DestroyAllAi extends SpellAbilityAi {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Player> defined = AbilityUtils.getDefinedPlayers(source, sa.getParam("Defined"), sa);
|
||||
|
||||
if (!defined.contains(opp)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -30,13 +30,12 @@ public class PowerExchangeAi extends SpellAbilityAi {
|
||||
sa.resetTargets();
|
||||
|
||||
List<Card> 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<Card>() {
|
||||
@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);
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)) {
|
||||
return false;
|
||||
}
|
||||
@@ -64,15 +58,9 @@ public class PumpAllAi extends PumpAiBase {
|
||||
}
|
||||
}
|
||||
|
||||
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<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()) {
|
||||
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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -46,29 +26,19 @@ public class SacrificeAllAi extends SpellAbilityAi {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<GameObject> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1247,17 +1247,6 @@ public class Card extends GameEntity implements Comparable<Card>, 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);
|
||||
|
||||
@@ -421,6 +421,9 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user