mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Fix casting SA while refusing to pay CostSacrifice
This commit is contained in:
@@ -354,7 +354,6 @@ public class AiCostDecision extends CostDecisionMakerBase {
|
||||
return PaymentDecision.number(c);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PaymentDecision visit(CostPutCardToLib cost) {
|
||||
if (cost.payCostFromSource()) {
|
||||
|
||||
@@ -331,11 +331,14 @@ public class ComputerUtil {
|
||||
}
|
||||
|
||||
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();
|
||||
String prefDef = "";
|
||||
if (activate != null) {
|
||||
prefDef = activate.getSVar("AIPreference");
|
||||
final String[] prefGroups = activate.getSVar("AIPreference").split("\\|");
|
||||
final String[] prefGroups = prefDef.split("\\|");
|
||||
for (String prefGroup : prefGroups) {
|
||||
final String[] prefValid = prefGroup.trim().split("\\$");
|
||||
if (prefValid[0].equals(pref) && !prefValid[1].startsWith("Special:")) {
|
||||
@@ -346,8 +349,8 @@ public class ComputerUtil {
|
||||
|
||||
for (String validItem : prefValid[1].split(",")) {
|
||||
final CardCollection prefList = CardLists.getValidCards(typeList, validItem, activate.getController(), activate, null);
|
||||
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold");
|
||||
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold");
|
||||
int threshold = getAIPreferenceParameter(activate, "CreatureEvalThreshold", sa);
|
||||
int minNeeded = getAIPreferenceParameter(activate, "MinCreaturesBelowThreshold", sa);
|
||||
|
||||
if (threshold != -1) {
|
||||
List<Card> toRemove = Lists.newArrayList();
|
||||
@@ -390,7 +393,7 @@ public class ComputerUtil {
|
||||
final CardCollection sacMeList = CardLists.filter(typeList, new Predicate<Card>() {
|
||||
@Override
|
||||
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()) {
|
||||
@@ -419,6 +422,7 @@ public class ComputerUtil {
|
||||
if (!nonCreatures.isEmpty()) {
|
||||
return ComputerUtilCard.getWorstAI(nonCreatures);
|
||||
} else if (!typeList.isEmpty()) {
|
||||
// TODO make sure survival is possible in case the creature blocks a trampler
|
||||
return ComputerUtilCard.getWorstAI(typeList);
|
||||
}
|
||||
}
|
||||
@@ -505,7 +509,7 @@ public class ComputerUtil {
|
||||
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")) {
|
||||
return -1;
|
||||
}
|
||||
@@ -520,7 +524,21 @@ public class ComputerUtil {
|
||||
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
|
||||
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;
|
||||
case "MinCreaturesBelowThreshold":
|
||||
@@ -543,9 +561,8 @@ public class ComputerUtil {
|
||||
|
||||
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
|
||||
|
||||
if ((target != null) && target.getController() == ai) {
|
||||
typeList.remove(target); // don't sacrifice the card we're pumping
|
||||
}
|
||||
// don't sacrifice the card we're pumping
|
||||
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, ability, ai);
|
||||
|
||||
if (typeList.size() < amount) {
|
||||
return null;
|
||||
@@ -573,9 +590,8 @@ public class ComputerUtil {
|
||||
final Card target, final int amount, SpellAbility sa) {
|
||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
|
||||
|
||||
if ((target != null) && target.getController() == ai) {
|
||||
typeList.remove(target); // don't exile the card we're pumping
|
||||
}
|
||||
// don't exile the card we're pumping
|
||||
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
|
||||
|
||||
if (typeList.size() < amount) {
|
||||
return null;
|
||||
@@ -594,9 +610,8 @@ public class ComputerUtil {
|
||||
final Card target, final int amount, SpellAbility sa) {
|
||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
|
||||
|
||||
if ((target != null) && target.getController() == ai) {
|
||||
typeList.remove(target); // don't move the card we're pumping
|
||||
}
|
||||
// don't move the card we're pumping
|
||||
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
|
||||
|
||||
if (typeList.size() < amount) {
|
||||
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) {
|
||||
final CardCollection typeList =
|
||||
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
||||
if ((target != null) && target.getController() == ai) {
|
||||
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
|
||||
|
||||
// don't bounce the card we're pumping
|
||||
typeList.remove(target);
|
||||
}
|
||||
typeList = ComputerUtilCost.paymentChoicesWithoutTargets(typeList, sa, ai);
|
||||
|
||||
if (typeList.size() < amount) {
|
||||
return new CardCollection();
|
||||
@@ -743,7 +756,7 @@ public class ComputerUtil {
|
||||
CardCollection remaining = new CardCollection(cardlist);
|
||||
final CardCollection sacrificed = new CardCollection();
|
||||
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(!source.getActivatingPlayer().isOpponentOf(ai)) {
|
||||
@@ -3026,6 +3039,6 @@ public class ComputerUtil {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
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.Sets;
|
||||
|
||||
@@ -20,6 +22,7 @@ import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.card.CardFactoryUtil;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardPredicates;
|
||||
import forge.game.card.CardPredicates.Presets;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterEnumType;
|
||||
@@ -270,7 +273,10 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
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;
|
||||
while (count < amount) {
|
||||
@@ -320,11 +326,14 @@ public class ComputerUtilCost {
|
||||
}
|
||||
|
||||
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;
|
||||
while (count < amount) {
|
||||
Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList);
|
||||
Card prefCard = ComputerUtil.getCardPreference(ai, source, "SacCost", typeList, sourceAbility);
|
||||
if (prefCard == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -407,7 +416,7 @@ public class ComputerUtilCost {
|
||||
* @return true, if successful
|
||||
*/
|
||||
public static boolean checkSacrificeCost(final Player ai, final Cost cost, final Card source, final SpellAbility sourceAbility) {
|
||||
return checkSacrificeCost(ai, cost, source, sourceAbility,true);
|
||||
return checkSacrificeCost(ai, cost, source, sourceAbility, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -420,8 +429,8 @@ public class ComputerUtilCost {
|
||||
* @param cost
|
||||
* @return a boolean.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean shouldPayCost(final Player ai, final Card hostCard, final Cost cost) {
|
||||
|
||||
for (final CostPart part : cost.getCostParts()) {
|
||||
if (part instanceof CostPayLife) {
|
||||
if (!ai.cantLoseForZeroOrLessLife()) {
|
||||
@@ -741,4 +750,12 @@ public class ComputerUtilCost {
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,11 +64,9 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
*/
|
||||
@Override
|
||||
protected boolean willPayCosts(Player ai, SpellAbility sa, Cost cost, Card source) {
|
||||
|
||||
final String type = sa.getParam("CounterType");
|
||||
final String aiLogic = sa.getParamOrDefault("AILogic", "");
|
||||
|
||||
// TODO Auto-generated method stub
|
||||
if (!super.willPayCosts(ai, sa, cost, source)) {
|
||||
return false;
|
||||
}
|
||||
@@ -225,8 +223,7 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
}
|
||||
|
||||
if (sa.canTarget(ai)) {
|
||||
// don't target itself when its forced to add poison
|
||||
// counters too
|
||||
// don't target itself when its forced to add poison counters too
|
||||
if (!ai.getCounters().isEmpty()) {
|
||||
if (!eachExisting || ai.getPoisonCounters() < 5) {
|
||||
sa.getTargets().add(ai);
|
||||
@@ -480,7 +477,6 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
list = CardLists.filter(list, new Predicate<Card>() {
|
||||
@Override
|
||||
public boolean apply(final Card c) {
|
||||
|
||||
// don't put the counter on the dead creature
|
||||
if (sacSelf && c.equals(source)) {
|
||||
return false;
|
||||
@@ -493,6 +489,8 @@ public class CountersPutAi extends SpellAbilityAi {
|
||||
Card sacTarget = ComputerUtil.getCardPreference(ai, source, "SacCost", list);
|
||||
// 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)
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -540,8 +540,7 @@ public class PumpAi extends PumpAiBase {
|
||||
|
||||
list = CardLists.getValidCards(list, tgt.getValidTgts(), ai, source, sa);
|
||||
if (game.getStack().isEmpty()) {
|
||||
// If the cost is tapping, don't activate before declare
|
||||
// attack/block
|
||||
// If the cost is tapping, don't activate before declare attack/block
|
||||
if (sa.getPayCosts().hasTapCost()) {
|
||||
if (game.getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
|
||||
&& game.getPhaseHandler().isPlayerTurn(ai)) {
|
||||
|
||||
@@ -23,7 +23,6 @@ public class SacrificeAi extends SpellAbilityAi {
|
||||
|
||||
@Override
|
||||
protected boolean canPlayAI(Player ai, SpellAbility sa) {
|
||||
|
||||
return sacrificeTgtAI(ai, sa);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ public class PaymentDecision {
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
public static PaymentDecision number(int c) {
|
||||
return new PaymentDecision(c);
|
||||
}
|
||||
@@ -77,7 +76,6 @@ public class PaymentDecision {
|
||||
return new PaymentDecision(null, manas, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@@ -7,5 +7,6 @@ S:Mode$ Continuous | Affected$ Creature.ChosenType+YouCtrl | AddPower$ 1 | AddTo
|
||||
AI:RemoveDeck:Random
|
||||
SVar:PlayMain1:TRUE
|
||||
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.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user