Fix casting SA while refusing to pay CostSacrifice

This commit is contained in:
tool4EvEr
2021-06-14 23:34:38 +02:00
parent 4049a3eea3
commit 1c0c0ad38d
9 changed files with 66 additions and 42 deletions

View File

@@ -354,7 +354,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
return PaymentDecision.number(c); return PaymentDecision.number(c);
} }
@Override @Override
public PaymentDecision visit(CostPutCardToLib cost) { public PaymentDecision visit(CostPutCardToLib cost) {
if (cost.payCostFromSource()) { if (cost.payCostFromSource()) {

View File

@@ -331,11 +331,14 @@ public class ComputerUtil {
} }
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) { public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList) {
return getCardPreference(ai, activate, pref, typeList, null);
}
public static Card getCardPreference(final Player ai, final Card activate, final String pref, final CardCollection typeList, SpellAbility sa) {
final Game game = ai.getGame(); final Game game = ai.getGame();
String prefDef = ""; String prefDef = "";
if (activate != null) { if (activate != null) {
prefDef = activate.getSVar("AIPreference"); prefDef = activate.getSVar("AIPreference");
final String[] prefGroups = activate.getSVar("AIPreference").split("\\|"); final String[] prefGroups = prefDef.split("\\|");
for (String prefGroup : prefGroups) { for (String prefGroup : prefGroups) {
final String[] prefValid = prefGroup.trim().split("\\$"); final String[] prefValid = prefGroup.trim().split("\\$");
if (prefValid[0].equals(pref) && !prefValid[1].startsWith("Special:")) { if (prefValid[0].equals(pref) && !prefValid[1].startsWith("Special:")) {
@@ -346,8 +349,8 @@ public class ComputerUtil {
for (String validItem : prefValid[1].split(",")) { for (String validItem : prefValid[1].split(",")) {
final CardCollection prefList = CardLists.getValidCards(typeList, validItem, activate.getController(), activate, null); final CardCollection prefList = CardLists.getValidCards(typeList, validItem, activate.getController(), activate, null);
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold"); int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold", sa);
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold"); int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold", sa);
if (threshold != -1) { if (threshold != -1) {
List<Card> toRemove = Lists.newArrayList(); List<Card> toRemove = Lists.newArrayList();
@@ -390,7 +393,7 @@ public class ComputerUtil {
final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() { final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
return (c.hasSVar("SacMe") && (Integer.parseInt(c.getSVar("SacMe")) == priority)); return c.hasSVar("SacMe") && (Integer.parseInt(c.getSVar("SacMe")) == priority);
} }
}); });
if (!sacMeList.isEmpty()) { if (!sacMeList.isEmpty()) {
@@ -419,6 +422,7 @@ public class ComputerUtil {
if (!nonCreatures.isEmpty()) { if (!nonCreatures.isEmpty()) {
return ComputerUtilCard.getWorstAI(nonCreatures); return ComputerUtilCard.getWorstAI(nonCreatures);
} else if (!typeList.isEmpty()) { } else if (!typeList.isEmpty()) {
// TODO make sure survival is possible in case the creature blocks a trampler
return ComputerUtilCard.getWorstAI(typeList); return ComputerUtilCard.getWorstAI(typeList);
} }
} }
@@ -505,7 +509,7 @@ public class ComputerUtil {
return null; return null;
} }
public static int getAIPreferenceParameter(final Card c, final String paramName) { public static int getAIPreferenceParameter(final Card c, final String paramName, SpellAbility sa) {
if (!c.hasSVar("AIPreferenceParams")) { if (!c.hasSVar("AIPreferenceParams")) {
return -1; return -1;
} }
@@ -520,7 +524,21 @@ public class ComputerUtil {
case "CreatureEvalThreshold": case "CreatureEvalThreshold":
// Threshold of 150 is just below the level of a 1/1 mana dork or a 2/2 baseline creature with no keywords // Threshold of 150 is just below the level of a 1/1 mana dork or a 2/2 baseline creature with no keywords
if (paramName.equals(parName)) { if (paramName.equals(parName)) {
return Integer.parseInt(parValue); int num = 0;
try {
num = Integer.parseInt(parValue);
} catch (NumberFormatException nfe) {
String[] valParts = StringUtils.split(parValue, "/");
CardCollection foundCards = AbilityUtils.getDefinedCards(c, valParts[0], sa);
if (!foundCards.isEmpty()) {
num = ComputerUtilCard.evaluateCreature(foundCards.get(0));
}
valParts[0] = Integer.toString(num);
if (valParts.length > 1) {
num = AbilityUtils.doXMath(num, valParts[1], c, sa);
}
}
return num;
} }
break; break;
case "MinCreaturesBelowThreshold": case "MinCreaturesBelowThreshold":
@@ -543,9 +561,8 @@ public class ComputerUtil {
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability)); typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
if ((target != null) && target.getController() == ai) { // don't sacrifice the card we're pumping
typeList.remove(target); // don't sacrifice the card we're pumping typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, ability, ai);
}
if (typeList.size() < amount) { if (typeList.size() < amount) {
return null; return null;
@@ -573,9 +590,8 @@ public class ComputerUtil {
final Card target, final int amount, SpellAbility sa) { final Card target, final int amount, SpellAbility sa) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa); CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) { // don't exile the card we're pumping
typeList.remove(target); // don't exile the card we're pumping typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
}
if (typeList.size() < amount) { if (typeList.size() < amount) {
return null; return null;
@@ -594,9 +610,8 @@ public class ComputerUtil {
final Card target, final int amount, SpellAbility sa) { final Card target, final int amount, SpellAbility sa) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa); CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) { // don't move the card we're pumping
typeList.remove(target); // don't move the card we're pumping typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
}
if (typeList.size() < amount) { if (typeList.size() < amount) {
return null; return null;
@@ -718,12 +733,10 @@ public class ComputerUtil {
} }
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) { public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) {
final CardCollection typeList = CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) {
// don't bounce the card we're pumping // don't bounce the card we're pumping
typeList.remove(target); typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
}
if (typeList.size() < amount) { if (typeList.size() < amount) {
return new CardCollection(); return new CardCollection();
@@ -743,7 +756,7 @@ public class ComputerUtil {
CardCollection remaining = new CardCollection(cardlist); CardCollection remaining = new CardCollection(cardlist);
final CardCollection sacrificed = new CardCollection(); final CardCollection sacrificed = new CardCollection();
final Card host = source.getHostCard(); final Card host = source.getHostCard();
final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold"); final int considerSacThreshold = getAIPreferenceParameter(host, "CreatureEvalThreshold", source);
if ("OpponentOnly".equals(source.getParam("AILogic"))) { if ("OpponentOnly".equals(source.getParam("AILogic"))) {
if(!source.getActivatingPlayer().isOpponentOf(ai)) { if(!source.getActivatingPlayer().isOpponentOf(ai)) {
@@ -3026,6 +3039,6 @@ public class ComputerUtil {
} }
} }
return false; return false;
}
} }
}

View File

@@ -8,6 +8,8 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
@@ -20,6 +22,7 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.card.CounterEnumType; import forge.game.card.CounterEnumType;
@@ -270,7 +273,10 @@ public class ComputerUtilCost {
} }
final CardCollection sacList = new CardCollection(); final CardCollection sacList = new CardCollection();
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility); CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
// don't sacrifice the card we're pumping
typeList = paymentChoicesWithoutTargets(typeList, sourceAbility, ai);
int count = 0; int count = 0;
while (count < amount) { while (count < amount) {
@@ -320,11 +326,14 @@ public class ComputerUtilCost {
} }
final CardCollection sacList = new CardCollection(); final CardCollection sacList = new CardCollection();
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility); CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
// don't sacrifice the card we're pumping
typeList = paymentChoicesWithoutTargets(typeList, sourceAbility, ai);
int count = 0; int count = 0;
while (count < amount) { while (count < amount) {
Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList); Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList, sourceAbility);
if (prefCard == null) { if (prefCard == null) {
return false; return false;
} }
@@ -420,8 +429,8 @@ public class ComputerUtilCost {
* @param cost * @param cost
* @return a boolean. * @return a boolean.
*/ */
@Deprecated
public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) { public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) {
for (final CostPart part : cost.getCostParts()) { for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostPayLife) { if (part instanceof CostPayLife) {
if (!ai.cantLoseForZeroOrLessLife()) { if (!ai.cantLoseForZeroOrLessLife()) {
@@ -741,4 +750,12 @@ public class ComputerUtilCost {
} }
return ObjectUtils.defaultIfNull(val, 0); return ObjectUtils.defaultIfNull(val, 0);
} }
public static CardCollection paymentChoicesWithoutTargets(Iterable<Card> choices, SpellAbility source, Player ai) {
if (source.usesTargeting()) {
final CardCollection targets = new CardCollection(source.getTargets().getTargetCards());
choices = Iterables.filter(choices, Predicates.not(Predicates.and(CardPredicates.isController(ai), Predicates.in(targets))));
}
return new CardCollection(choices);
}
} }

View File

@@ -64,11 +64,9 @@ public class CountersPutAi extends SpellAbilityAi {
*/ */
@Override @Override
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) { protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
final String type = sa.getParam("CounterType"); final String type = sa.getParam("CounterType");
final String aiLogic = sa.getParamOrDefault("AILogic", ""); final String aiLogic = sa.getParamOrDefault("AILogic", "");
// TODO Auto-generated method stub
if (!super.willPayCosts(ai, sa, cost, source)) { if (!super.willPayCosts(ai, sa, cost, source)) {
return false; return false;
} }
@@ -225,8 +223,7 @@ public class CountersPutAi extends SpellAbilityAi {
} }
if (sa.canTarget(ai)) { if (sa.canTarget(ai)) {
// don't target itself when its forced to add poison // don't target itself when its forced to add poison counters too
// counters too
if (!ai.getCounters().isEmpty()) { if (!ai.getCounters().isEmpty()) {
if (!eachExisting || ai.getPoisonCounters() < 5) { if (!eachExisting || ai.getPoisonCounters() < 5) {
sa.getTargets().add(ai); sa.getTargets().add(ai);
@@ -480,7 +477,6 @@ public class CountersPutAi extends SpellAbilityAi {
list = CardLists.filter(list, new Predicate<Card>() { list = CardLists.filter(list, new Predicate<Card>() {
@Override @Override
public boolean apply(final Card c) { public boolean apply(final Card c) {
// don't put the counter on the dead creature // don't put the counter on the dead creature
if (sacSelf && c.equals(source)) { if (sacSelf && c.equals(source)) {
return false; return false;
@@ -493,6 +489,8 @@ public class CountersPutAi extends SpellAbilityAi {
Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list); Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list);
// this card is planned to be sacrificed during cost payment, so don't target it // this card is planned to be sacrificed during cost payment, so don't target it
// (otherwise the AI can cheat by activating this SA and not paying the sac cost, e.g. Extruder) // (otherwise the AI can cheat by activating this SA and not paying the sac cost, e.g. Extruder)
// TODO needs update if amount > 1 gets printed,
// maybe also check putting the counter on that exact creature is more important than sacrificing it (though unlikely?)
list.remove(sacTarget); list.remove(sacTarget);
} }

View File

@@ -540,8 +540,7 @@ public class PumpAi extends PumpAiBase {
list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa); list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa);
if (game.getStack().isEmpty()) { if (game.getStack().isEmpty()) {
// If the cost is tapping, don't activate before declare // If the cost is tapping, don't activate before declare attack/block
// attack/block
if (sa.getPayCosts().hasTapCost()) { if (sa.getPayCosts().hasTapCost()) {
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS) if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& game.getPhaseHandler().isPlayerTurn(ai)) { && game.getPhaseHandler().isPlayerTurn(ai)) {

View File

@@ -23,7 +23,6 @@ public class SacrificeAi extends SpellAbilityAi {
@Override @Override
protected boolean canPlayAI(Player ai, SpellAbility sa) { protected boolean canPlayAI(Player ai, SpellAbility sa) {
return sacrificeTgtAI(ai, sa); return sacrificeTgtAI(ai, sa);
} }

View File

@@ -58,7 +58,6 @@ public class PaymentDecision {
return res; return res;
} }
public static PaymentDecision number(int c) { public static PaymentDecision number(int c) {
return new PaymentDecision(c); return new PaymentDecision(c);
} }
@@ -77,7 +76,6 @@ public class PaymentDecision {
return new PaymentDecision(null, manas, null, null, null); return new PaymentDecision(null, manas, null, null, null);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */

View File

@@ -7,5 +7,6 @@ S:Mode$ Continuous | Affected$ Creature.ChosenType+YouCtrl | AddPower$ 1 | AddTo
AI:RemoveDeck:Random AI:RemoveDeck:Random
SVar:PlayMain1:TRUE SVar:PlayMain1:TRUE
SVar:AIPreference:SacCost$Creature.ChosenType+Other SVar:AIPreference:SacCost$Creature.ChosenType+Other
SVar:AIPreferenceParams:CreatureEvalThreshold$ Targeted/Plus.10
A:AB$ Pump | Cost$ 1 Sac<1/Creature.ChosenType/creature of the chosen type> | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | KW$ Indestructible | SpellDescription$ Target creature you control gains indestructible until end of turn. A:AB$ Pump | Cost$ 1 Sac<1/Creature.ChosenType/creature of the chosen type> | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | KW$ Indestructible | SpellDescription$ Target creature you control gains indestructible until end of turn.
Oracle:As Etchings of the Chosen enters the battlefield, choose a creature type.\nCreatures you control of the chosen type get +1/+1.\n{1}, Sacrifice a creature of the chosen type: Target creature you control gains indestructible until end of turn. Oracle:As Etchings of the Chosen enters the battlefield, choose a creature type.\nCreatures you control of the chosen type get +1/+1.\n{1}, Sacrifice a creature of the chosen type: Target creature you control gains indestructible until end of turn.