diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 811e283cfdf..308314ca04a 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -2227,11 +2227,11 @@ public class AiController { private boolean checkAiSpecificRestrictions(final SpellAbility sa) { // AI-specific restrictions specified as activation parameters in spell abilities - + if (sa.hasParam("AILifeThreshold")) { return player.getLife() > Integer.parseInt(sa.getParam("AILifeThreshold")); } - + return true; } diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 4c8a9d9f98a..8058d6d973c 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -354,7 +354,6 @@ public class AiCostDecision extends CostDecisionMakerBase { return PaymentDecision.number(c); } - @Override public PaymentDecision visit(CostPutCardToLib cost) { if (cost.payCostFromSource()) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index e921f42fbda..cbbde47a278 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -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 toRemove = Lists.newArrayList(); @@ -390,7 +393,7 @@ public class ComputerUtil { final CardCollection sacMeList = CardLists.filter(typeList, new Predicate() { @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) { - // don't bounce the card we're pumping - typeList.remove(target); - } + CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa); + + // don't bounce the card we're pumping + 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; - } + } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 532764b0b1f..468da12f98d 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -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 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); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index c3b3c130950..a7639d5b81e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -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() { @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); } @@ -617,7 +615,7 @@ public class CountersPutAi extends SpellAbilityAi { // Instant +1/+1 if (type.equals("P1P1") && !SpellAbilityAi.isSorcerySpeed(sa)) { if (!(ph.getNextTurn() == ai && ph.is(PhaseType.END_OF_TURN) && abCost.isReusuableResource())) { - return false; // only if next turn and cost is reusable + return false; // only if next turn and cost is reusable } } } diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index 782ab272dc8..5582a1d16dc 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -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)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java index 36d0fa484c6..c977ba3c209 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SacrificeAi.java @@ -23,7 +23,6 @@ public class SacrificeAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player ai, SpellAbility sa) { - return sacrificeTgtAI(ai, sa); } diff --git a/forge-game/src/main/java/forge/game/cost/PaymentDecision.java b/forge-game/src/main/java/forge/game/cost/PaymentDecision.java index 743a4160d2e..bb0e7c2bb51 100644 --- a/forge-game/src/main/java/forge/game/cost/PaymentDecision.java +++ b/forge-game/src/main/java/forge/game/cost/PaymentDecision.java @@ -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() */ diff --git a/forge-gui/res/cardsfolder/e/etchings_of_the_chosen.txt b/forge-gui/res/cardsfolder/e/etchings_of_the_chosen.txt index 1da44289997..d0e7787aa28 100644 --- a/forge-gui/res/cardsfolder/e/etchings_of_the_chosen.txt +++ b/forge-gui/res/cardsfolder/e/etchings_of_the_chosen.txt @@ -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.