mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
DestroyAI: better logic for Pongify also Update for X
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
package forge.ai;
|
package forge.ai;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import forge.ai.ability.TokenAi;
|
import forge.ai.ability.TokenAi;
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
@@ -12,8 +16,6 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
import forge.util.Aggregates;
|
import forge.util.Aggregates;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -31,17 +33,34 @@ public class SpecialAiLogic {
|
|||||||
Card source = sa.getHostCard();
|
Card source = sa.getHostCard();
|
||||||
Game game = source.getGame();
|
Game game = source.getGame();
|
||||||
PhaseHandler ph = game.getPhaseHandler();
|
PhaseHandler ph = game.getPhaseHandler();
|
||||||
TargetRestrictions tgt = sa.getTargetRestrictions();
|
boolean isDestroy = ApiType.Destroy.equals(sa.getApi());
|
||||||
|
SpellAbility tokenSA = sa.findSubAbilityByType(ApiType.Token);
|
||||||
|
if (tokenSA == null) {
|
||||||
|
// Used wrong AI logic?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
CardCollection listOpp = CardLists.getValidCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa);
|
List<Card> targetable = CardUtil.getValidCardsToTarget(sa.getTargetRestrictions(), sa);
|
||||||
listOpp = CardLists.getTargetableCards(listOpp, sa);
|
|
||||||
|
|
||||||
Card choice = ComputerUtilCard.getMostExpensivePermanentAI(listOpp);
|
CardCollection listOpp = CardLists.filterControlledBy(targetable, ai.getOpponents());
|
||||||
|
if (isDestroy) {
|
||||||
|
listOpp = CardLists.getNotKeyword(listOpp, Keyword.INDESTRUCTIBLE);
|
||||||
|
// TODO add handling for cards like targeting dies
|
||||||
|
}
|
||||||
|
|
||||||
final Card token = choice != null ? TokenAi.spawnToken(choice.getController(), sa.getSubAbility()) : null;
|
Card choice = null;
|
||||||
if (token == null || !token.isCreature() || token.getNetToughness() < 1) {
|
if (!listOpp.isEmpty()) {
|
||||||
return true; // becomes Terminate
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(listOpp);
|
||||||
} else if (choice != null && choice.isPlaneswalker()) {
|
// can choice even be null?
|
||||||
|
|
||||||
|
if (choice != null) {
|
||||||
|
final Card token = TokenAi.spawnToken(choice.getController(), tokenSA);
|
||||||
|
if (!token.isCreature() || token.getNetToughness() < 1) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(choice);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (choice.isPlaneswalker()) {
|
||||||
if (choice.getCurrentLoyalty() * 35 > ComputerUtilCard.evaluateCreature(token)) {
|
if (choice.getCurrentLoyalty() * 35 > ComputerUtilCard.evaluateCreature(token)) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
@@ -49,42 +68,48 @@ public class SpecialAiLogic {
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
boolean hasOppTarget = true;
|
if ((!choice.isCreature() || choice.isTapped()) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.isPlayerTurn(ai) // prevent surprise combatant
|
||||||
if (choice != null
|
|
||||||
&& ((!choice.isCreature() || choice.isTapped()) && ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS) && ph.getPlayerTurn() == ai) // prevent surprise combatant
|
|
||||||
|| ComputerUtilCard.evaluateCreature(choice) < 1.5 * ComputerUtilCard.evaluateCreature(token)) {
|
|| ComputerUtilCard.evaluateCreature(choice) < 1.5 * ComputerUtilCard.evaluateCreature(token)) {
|
||||||
|
choice = null;
|
||||||
hasOppTarget = false;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if we have anything we can upgrade
|
// See if we have anything we can upgrade
|
||||||
if (!hasOppTarget) {
|
if (choice == null) {
|
||||||
CardCollection listOwn = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), ai, source, sa);
|
CardCollection listOwn = CardLists.filterControlledBy(targetable, ai);
|
||||||
listOwn = CardLists.getTargetableCards(listOwn, sa);
|
final Card token = TokenAi.spawnToken(ai, tokenSA);
|
||||||
|
|
||||||
Card bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, new Predicate<Card>() {
|
Card bestOwnCardToUpgrade = null;
|
||||||
|
if (isDestroy) {
|
||||||
|
// just choose any Indestructible
|
||||||
|
// TODO maybe filter something that doesn't like to be targeted, or does something benefit by targeting
|
||||||
|
bestOwnCardToUpgrade = Iterables.getFirst(CardLists.getKeyword(listOwn, Keyword.INDESTRUCTIBLE), null);
|
||||||
|
}
|
||||||
|
if (bestOwnCardToUpgrade == null) {
|
||||||
|
bestOwnCardToUpgrade = ComputerUtilCard.getWorstCreatureAI(CardLists.filter(listOwn, new Predicate<Card>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Card card) {
|
public boolean apply(Card card) {
|
||||||
return card.isCreature() && (ComputerUtilCard.isUselessCreature(ai, card)
|
return card.isCreature() && (ComputerUtilCard.isUselessCreature(ai, card)
|
||||||
|| ComputerUtilCard.evaluateCreature(token) > 2 * ComputerUtilCard.evaluateCreature(card));
|
|| ComputerUtilCard.evaluateCreature(token) > 2 * ComputerUtilCard.evaluateCreature(card));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
if (bestOwnCardToUpgrade != null) {
|
if (bestOwnCardToUpgrade != null) {
|
||||||
if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || ph.getPlayerTurn() != ai)) {
|
if (ComputerUtilCard.isUselessCreature(ai, bestOwnCardToUpgrade) || (ph.getPhase().isAfter(PhaseType.COMBAT_END) || !ph.isPlayerTurn(ai))) {
|
||||||
sa.resetTargets();
|
choice = bestOwnCardToUpgrade;
|
||||||
sa.getTargets().add(bestOwnCardToUpgrade);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (choice != null) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(choice);
|
sa.getTargets().add(choice);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasOppTarget;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A logic for cards that say "Sacrifice a creature: CARDNAME gets +X/+X until EOT"
|
// A logic for cards that say "Sacrifice a creature: CARDNAME gets +X/+X until EOT"
|
||||||
|
|||||||
@@ -78,10 +78,14 @@ public abstract class SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!checkApiLogic(ai, sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// needs to be after API logic because needs to check possible X Cost?
|
||||||
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
if (cost != null && !willPayCosts(ai, sa, cost, source)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return checkApiLogic(ai, sa);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
protected boolean checkConditions(final Player ai, final SpellAbility sa, SpellAbilityCondition con) {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import forge.game.phase.PhaseHandler;
|
|||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
|
|
||||||
public class DestroyAi extends SpellAbilityAi {
|
public class DestroyAi extends SpellAbilityAi {
|
||||||
@@ -23,89 +22,19 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean canPlayAI(final Player ai, SpellAbility sa) {
|
protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) {
|
||||||
// AI needs to be expanded, since this function can be pretty complex
|
|
||||||
// based on what the expected targets could be
|
|
||||||
final Cost abCost = sa.getPayCosts();
|
|
||||||
final TargetRestrictions abTgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final boolean noRegen = sa.hasParam("NoRegen");
|
if (sa.usesTargeting()) {
|
||||||
final String logic = sa.getParam("AILogic");
|
|
||||||
boolean hasXCost = false;
|
|
||||||
|
|
||||||
CardCollection list;
|
|
||||||
|
|
||||||
if (abCost != null) {
|
|
||||||
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasXCost = sa.costHasManaX();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("AtOpponentsCombatOrAfter".equals(sa.getParam("AILogic"))) {
|
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
|
||||||
if (ph.getPlayerTurn() == ai || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if ("AtEOT".equals(sa.getParam("AILogic"))) {
|
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
|
||||||
if (!ph.is(PhaseType.END_OF_TURN)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if ("AtEOTIfNotAttacking".equals(sa.getParam("AILogic"))) {
|
|
||||||
PhaseHandler ph = ai.getGame().getPhaseHandler();
|
|
||||||
if (!ph.is(PhaseType.END_OF_TURN) || !ai.getCreaturesAttackedThisTurn().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ability that's intended to destroy own useless token to trigger Grave Pacts
|
|
||||||
// should be fired at end of turn or when under attack after blocking to make opponent sac something
|
|
||||||
boolean havepact = false;
|
|
||||||
|
|
||||||
// TODO replace it with look for a dies -> sacrifice trigger check
|
|
||||||
havepact |= ai.isCardInPlay("Grave Pact");
|
|
||||||
havepact |= ai.isCardInPlay("Butcher of Malakir");
|
|
||||||
havepact |= ai.isCardInPlay("Dictate of Erebos");
|
|
||||||
if ("Pactivator".equals(logic) && havepact) {
|
|
||||||
if ((!ai.getGame().getPhaseHandler().isPlayerTurn(ai))
|
|
||||||
&& ((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) || (ai.getGame().getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)))
|
|
||||||
&& (ai.getOpponents().getCreaturesInPlay().size() > 0)) {
|
|
||||||
ai.getController().chooseTargetsFor(sa);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Targeting
|
|
||||||
if (abTgt != null) {
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
if (sa.hasParam("TargetingPlayer")) {
|
if ("MadSarkhanDragon".equals(aiLogic)) {
|
||||||
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
|
||||||
sa.setTargetingPlayer(targetingPlayer);
|
|
||||||
return targetingPlayer.getController().chooseTargetsFor(sa);
|
|
||||||
}
|
|
||||||
if ("MadSarkhanDragon".equals(logic)) {
|
|
||||||
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
return SpecialCardAi.SarkhanTheMad.considerMakeDragon(ai, sa);
|
||||||
} else if (logic != null && logic.startsWith("MinLoyalty.")) {
|
} else if (aiLogic.startsWith("MinLoyalty.")) {
|
||||||
int minLoyalty = Integer.parseInt(logic.substring(logic.indexOf(".") + 1));
|
int minLoyalty = Integer.parseInt(aiLogic.substring(aiLogic.indexOf(".") + 1));
|
||||||
if (source.getCounters(CounterEnumType.LOYALTY) < minLoyalty) {
|
if (source.getCounters(CounterEnumType.LOYALTY) < minLoyalty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if ("Polymorph".equals(logic)) {
|
} else if ("Polymorph".equals(aiLogic)) {
|
||||||
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -124,7 +53,108 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
sa.getTargets().add(worst);
|
sa.getTargets().add(worst);
|
||||||
return true;
|
return true;
|
||||||
|
} else if ("Pongify".equals(aiLogic)) {
|
||||||
|
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return super.checkAiLogic(ai, sa, aiLogic);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean checkPhaseRestrictions(final Player ai, final SpellAbility sa, final PhaseHandler ph,
|
||||||
|
final String logic) {
|
||||||
|
if ("AtOpponentsCombatOrAfter".equals(logic)) {
|
||||||
|
if (ph.isPlayerTurn(ai) || ph.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if ("AtEOT".equals(logic)) {
|
||||||
|
if (!ph.is(PhaseType.END_OF_TURN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if ("AtEOTIfNotAttacking".equals(logic)) {
|
||||||
|
if (!ph.is(PhaseType.END_OF_TURN) || !ai.getCreaturesAttackedThisTurn().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if ("Pactivator".equals(logic)) {
|
||||||
|
// Ability that's intended to destroy own useless token to trigger Grave Pacts
|
||||||
|
// should be fired at end of turn or when under attack after blocking to make opponent sac something
|
||||||
|
boolean havepact = false;
|
||||||
|
|
||||||
|
// TODO replace it with look for a dies -> sacrifice trigger check
|
||||||
|
havepact |= ai.isCardInPlay("Grave Pact");
|
||||||
|
havepact |= ai.isCardInPlay("Butcher of Malakir");
|
||||||
|
havepact |= ai.isCardInPlay("Dictate of Erebos");
|
||||||
|
if (havepact) {
|
||||||
|
if ((!ph.isPlayerTurn(ai))
|
||||||
|
&& ((ph.is(PhaseType.END_OF_TURN)) || (ph.is(PhaseType.COMBAT_DECLARE_BLOCKERS)))
|
||||||
|
&& (ai.getOpponents().getCreaturesInPlay().size() > 0)) {
|
||||||
|
CardCollection list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
|
Card worst = ComputerUtilCard.getWorstAI(list);
|
||||||
|
if (worst != null) {
|
||||||
|
sa.getTargets().add(worst);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
|
final Card source = sa.getHostCard();
|
||||||
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
|
final String logic = sa.getParam("AILogic");
|
||||||
|
|
||||||
|
CardCollection list;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Targeting
|
||||||
|
if (sa.usesTargeting()) {
|
||||||
|
// Assume there where already enough targets chosen by AI Logic Above
|
||||||
|
if (!sa.canAddMoreTarget() && sa.isTargetNumberValid()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset targets before AI Logic part
|
||||||
|
sa.resetTargets();
|
||||||
|
int maxTargets;
|
||||||
|
|
||||||
|
if (sa.costHasManaX()) {
|
||||||
|
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
||||||
|
maxTargets = ComputerUtilCost.getMaxXValue(sa, ai);
|
||||||
|
// need to set XPaid to get the right number for
|
||||||
|
sa.setXManaCostPaid(maxTargets);
|
||||||
|
// need to check for maxTargets
|
||||||
|
maxTargets = Math.min(maxTargets, sa.getMaxTargets());
|
||||||
|
} else {
|
||||||
|
maxTargets = sa.getMaxTargets();
|
||||||
|
}
|
||||||
|
if (sa.hasParam("AIMaxTgtsCount")) {
|
||||||
|
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
|
||||||
|
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
|
||||||
|
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxTargets == 0) {
|
||||||
|
// can't afford X or otherwise target anything
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("TargetingPlayer")) {
|
||||||
|
Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0);
|
||||||
|
sa.setTargetingPlayer(targetingPlayer);
|
||||||
|
return targetingPlayer.getController().chooseTargetsFor(sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI doesn't destroy own cards if it isn't defined in AI logic
|
||||||
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
list = CardLists.getTargetableCards(ai.getOpponents().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if ("FatalPush".equals(logic)) {
|
if ("FatalPush".equals(logic)) {
|
||||||
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
final int cmcMax = ai.hasRevolt() ? 4 : 2;
|
||||||
@@ -184,33 +214,12 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int maxTargets = abTgt.getMaxTargets(sa.getHostCard(), sa);
|
|
||||||
|
|
||||||
if (hasXCost) {
|
|
||||||
// TODO: currently the AI will maximize mana spent on X, trying to maximize damage. This may need improvement.
|
|
||||||
maxTargets = Math.min(ComputerUtilMana.determineMaxAffordableX(ai, sa), abTgt.getMaxTargets(sa.getHostCard(), sa));
|
|
||||||
// X can't be more than the lands we have in our hand for "discard X lands"!
|
|
||||||
if ("ScorchedEarth".equals(logic)) {
|
|
||||||
int lands = CardLists.filter(ai.getCardsIn(ZoneType.Hand), CardPredicates.Presets.LANDS).size();
|
|
||||||
maxTargets = Math.min(maxTargets, lands);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sa.hasParam("AIMaxTgtsCount")) {
|
|
||||||
// Cards that have confusing costs for the AI (e.g. Eliminate the Competition) can have forced max target constraints specified
|
|
||||||
// TODO: is there a better way to predict things like "sac X" costs without needing a special AI variable?
|
|
||||||
maxTargets = Math.min(CardFactoryUtil.xCount(sa.getHostCard(), "Count$" + sa.getParam("AIMaxTgtsCount")), maxTargets);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxTargets == 0) {
|
|
||||||
// can't afford X or otherwise target anything
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// target loop
|
// target loop
|
||||||
|
// TODO use can add more Targets
|
||||||
while (sa.getTargets().size() < maxTargets) {
|
while (sa.getTargets().size() < maxTargets) {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if ((sa.getTargets().size() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
|| (sa.getTargets().size() == 0)) {
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -222,10 +231,6 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
Card choice = null;
|
Card choice = null;
|
||||||
// If the targets are only of one type, take the best
|
// If the targets are only of one type, take the best
|
||||||
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
if (CardLists.getNotType(list, "Creature").isEmpty()) {
|
||||||
if ("Pongify".equals(logic)) {
|
|
||||||
return SpecialAiLogic.doPongifyLogic(ai, sa);
|
|
||||||
}
|
|
||||||
|
|
||||||
choice = ComputerUtilCard.getBestCreatureAI(list);
|
choice = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
if ("OppDestroyYours".equals(logic)) {
|
if ("OppDestroyYours".equals(logic)) {
|
||||||
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
Card aiBest = ComputerUtilCard.getBestCreatureAI(ai.getCreaturesInPlay());
|
||||||
@@ -246,15 +251,14 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
choice = ComputerUtilCard.getMostExpensivePermanentAI(list, sa, true);
|
||||||
}
|
}
|
||||||
//option to hold removal instead only applies for single targeted removal
|
//option to hold removal instead only applies for single targeted removal
|
||||||
if (!sa.isTrigger() && abTgt.getMaxTargets(sa.getHostCard(), sa) == 1) {
|
if (!sa.isTrigger() && sa.getMaxTargets() == 1) {
|
||||||
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
if (choice == null || !ComputerUtilCard.useRemovalNow(sa, choice, 0, ZoneType.Graveyard)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choice == null) { // can't find anything left
|
if (choice == null) { // can't find anything left
|
||||||
if ((sa.getTargets().size() < abTgt.getMinTargets(sa.getHostCard(), sa))
|
if (!sa.isMinTargetChosen() || sa.isZeroTargets()) {
|
||||||
|| (sa.getTargets().size() == 0)) {
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -298,22 +302,19 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
final TargetRestrictions tgt = sa.getTargetRestrictions();
|
|
||||||
final Card source = sa.getHostCard();
|
|
||||||
final boolean noRegen = sa.hasParam("NoRegen");
|
final boolean noRegen = sa.hasParam("NoRegen");
|
||||||
if (tgt != null) {
|
if (sa.usesTargeting()) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
CardCollection list = CardLists.getTargetableCards(ai.getGame().getCardsIn(ZoneType.Battlefield), sa);
|
||||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source, sa);
|
|
||||||
|
if (list.isEmpty() || list.size() < sa.getMinTargets()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to avoid targeting creatures that are dead on board
|
// Try to avoid targeting creatures that are dead on board
|
||||||
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list, sa);
|
list = ComputerUtil.filterCreaturesThatWillDieThisTurn(ai, list, sa);
|
||||||
|
|
||||||
if (list.isEmpty() || list.size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CardCollection preferred = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
CardCollection preferred = CardLists.getNotKeyword(list, Keyword.INDESTRUCTIBLE);
|
||||||
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
preferred = CardLists.filterControlledBy(preferred, ai.getOpponents());
|
||||||
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
if (CardLists.getNotType(preferred, "Creature").isEmpty()) {
|
||||||
@@ -344,10 +345,9 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().size() < tgt.getMaxTargets(sa.getHostCard(), sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (preferred.isEmpty()) {
|
if (preferred.isEmpty()) {
|
||||||
if (sa.getTargets().size() == 0
|
if (!sa.isMinTargetChosen()) {
|
||||||
|| sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
return false;
|
return false;
|
||||||
@@ -371,7 +371,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.getTargets().size() < tgt.getMinTargets(sa.getHostCard(), sa)) {
|
while (sa.canAddMoreTarget()) {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
@@ -392,7 +392,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sa.getTargets().size() >= tgt.getMinTargets(sa.getHostCard(), sa);
|
return sa.isTargetNumberValid();
|
||||||
} else {
|
} else {
|
||||||
return mandatory;
|
return mandatory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,16 @@ public class CostDiscard extends CostPartWithList {
|
|||||||
|
|
||||||
public int paymentOrder() { return 10; }
|
public int paymentOrder() { return 10; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getMaxAmountX(SpellAbility ability, Player payer) {
|
||||||
|
final Card source = ability.getHostCard();
|
||||||
|
String type = this.getType();
|
||||||
|
CardCollectionView handList = payer.canDiscardBy(ability) ? payer.getCardsIn(ZoneType.Hand) : CardCollection.EMPTY;
|
||||||
|
|
||||||
|
handList = CardLists.getValidCards(handList, type.split(";"), payer, source, ability);
|
||||||
|
return handList.size();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1391,7 +1391,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getTargets().size() < getTargetRestrictions().getMaxTargets(hostCard, this);
|
return getTargets().size() < getMaxTargets();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isZeroTargets() {
|
public boolean isZeroTargets() {
|
||||||
@@ -1405,6 +1405,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
return getTargetRestrictions().isMaxTargetsChosen(hostCard, this);
|
return getTargetRestrictions().isMaxTargetsChosen(hostCard, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMinTargets() {
|
||||||
|
return getTargetRestrictions().getMinTargets(getHostCard(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxTargets() {
|
||||||
|
return getTargetRestrictions().getMaxTargets(getHostCard(), this);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isTargetNumberValid() {
|
public boolean isTargetNumberValid() {
|
||||||
if (!this.usesTargeting()) {
|
if (!this.usesTargeting()) {
|
||||||
return getTargets().isEmpty();
|
return getTargets().isEmpty();
|
||||||
@@ -1413,7 +1421,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
if (!isMinTargetChosen()) {
|
if (!isMinTargetChosen()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int maxTargets = getTargetRestrictions().getMaxTargets(hostCard, this);
|
int maxTargets = getMaxTargets();
|
||||||
|
|
||||||
if (maxTargets == 0 && getPayCosts().hasSpecificCostType(CostRemoveCounter.class)
|
if (maxTargets == 0 && getPayCosts().hasSpecificCostType(CostRemoveCounter.class)
|
||||||
&& hasSVar(getParam("TargetMax"))
|
&& hasSVar(getParam("TargetMax"))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Name:Scorched Earth
|
Name:Scorched Earth
|
||||||
ManaCost:X R
|
ManaCost:X R
|
||||||
Types:Sorcery
|
Types:Sorcery
|
||||||
A:SP$ Destroy | Cost$ X R Discard<X/Land/land card(s)> | CostDesc$ As an additional cost to cast this spell, discard X land cards. | TargetMin$ X | TargetMax$ X | ValidTgts$ Land | TgtPrompt$ Select X target lands | References$ X | SpellDescription$ Destroy X target lands. | AILogic$ ScorchedEarth
|
A:SP$ Destroy | Cost$ X R Discard<X/Land/land card(s)> | CostDesc$ As an additional cost to cast this spell, discard X land cards. | TargetMin$ X | TargetMax$ X | ValidTgts$ Land | TgtPrompt$ Select X target lands | References$ X | SpellDescription$ Destroy X target lands.
|
||||||
SVar:X:Count$xPaid
|
SVar:X:Count$xPaid
|
||||||
AI:RemoveDeck:Random
|
AI:RemoveDeck:Random
|
||||||
SVar:PlayBeforeLandDrop:true
|
SVar:PlayBeforeLandDrop:true
|
||||||
|
|||||||
Reference in New Issue
Block a user