diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index d1ac0f4deac..64e38102def 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -849,20 +849,22 @@ public class AiController { int neededMana = 0; boolean dangerousRecurringCost = false; - for (SpellAbility sa2 : GameActionUtil.getOptionalCosts(sa)) { - if (sa2.isOptionalCostPaid(OptionalCost.Buyback)) { - Cost sac = sa2.getPayCosts(); - CostAdjustment.adjust(sac, sa2); - if (sac.getCostMana() != null) { - neededMana = sac.getCostMana().getMana().getCMC(); - } - if (sac.hasSpecificCostType(CostPayLife.class) - || sac.hasSpecificCostType(CostDiscard.class) - || sac.hasSpecificCostType(CostSacrifice.class)) { - dangerousRecurringCost = true; - } + + Cost costWithBuyback = sa.getPayCosts().copy(); + for (OptionalCostValue opt : GameActionUtil.getOptionalCostValues(sa)) { + if (opt.getType() == OptionalCost.Buyback) { + costWithBuyback.add(opt.getCost()); } } + CostAdjustment.adjust(costWithBuyback, sa); + if (costWithBuyback.getCostMana() != null) { + neededMana = costWithBuyback.getCostMana().getMana().getCMC(); + } + if (costWithBuyback.hasSpecificCostType(CostPayLife.class) + || costWithBuyback.hasSpecificCostType(CostDiscard.class) + || costWithBuyback.hasSpecificCostType(CostSacrifice.class)) { + dangerousRecurringCost = true; + } // won't be able to afford buyback any time soon // if Buyback cost includes sacrifice, life, discard @@ -1568,7 +1570,7 @@ public class AiController { sa.setActivatingPlayer(player); sa.setLastStateBattlefield(game.getLastStateBattlefield()); sa.setLastStateGraveyard(game.getLastStateGraveyard()); - + AiPlayDecision opinion = canPlayAndPayFor(sa); // PhaseHandler ph = game.getPhaseHandler(); // System.out.printf("Ai thinks '%s' of %s -> %s @ %s %s >>> \n", opinion, sa.getHostCard(), sa, Lang.getPossesive(ph.getPlayerTurn().getName()), ph.getPhase()); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java index 96ab2e19a88..366e53865a4 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java @@ -105,17 +105,22 @@ public class ComputerUtilAbility { final List result = Lists.newArrayList(); for (SpellAbility sa : newAbilities) { sa.setActivatingPlayer(player); - // TODO: remove this once all optional costs are ported over to chooseOptionalCosts - result.addAll(GameActionUtil.getOptionalCosts(sa)); // Optional cost selection through the AI controller + boolean choseOptCost = false; List list = GameActionUtil.getOptionalCostValues(sa); if (!list.isEmpty()) { list = player.getController().chooseOptionalCosts(sa, list); if (!list.isEmpty()) { + choseOptCost = true; result.add(GameActionUtil.addOptionalCosts(sa, list)); } } + + // Add only one ability: either the one with preferred optional costs, or the original one if there are none + if (!choseOptCost) { + result.add(sa); + } } return result; diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 245d10f472e..18370eb882a 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -1147,19 +1147,20 @@ public class PlayerControllerAi extends PlayerController { public List chooseOptionalCosts(SpellAbility chosen, List optionalCostValues) { List chosenOptCosts = Lists.newArrayList(); - Cost cost = chosen.getPayCosts(); + Cost costSoFar = chosen.getPayCosts() != null ? chosen.getPayCosts().copy() : Cost.Zero; for (OptionalCostValue opt : optionalCostValues) { if (opt.getType() == OptionalCost.Entwine) { - // Test implementation: just always choose Entwine - Cost fullCost = opt.getCost().copy().add(cost); - SpellAbility fullCostSa = chosen.copyWithDefinedCost(fullCost); - if (ComputerUtilCost.canPayCost(fullCostSa, player)) { - chosenOptCosts.add(opt); - } + // Specific code for optional costs should be made conditional here } - else { - System.out.println("Skipping unported optional cost: " + opt.getType()); + + // Generic code: for now, chooses the optional cost if it can be paid (and later - played) + Cost fullCost = opt.getCost().copy().add(costSoFar); + SpellAbility fullCostSa = chosen.copyWithDefinedCost(fullCost); + if (ComputerUtilCost.canPayCost(fullCostSa, player)) { + chosenOptCosts.add(opt); + costSoFar.add(opt.getCost()); + System.out.println("Chosen: " + opt + " for total cost of " + costSoFar.toSimpleString()); } } diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 8c098e516e4..2661c66a038 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -345,138 +345,6 @@ public final class GameActionUtil { return abilities; } - - /** - * get optional additional costs. - * - * @param original - * the original sa - * @return an ArrayList. - * - * @deprecated only used by AI, replace it with new functions in AI - */ - @Deprecated public static List getOptionalCosts(final SpellAbility original) { - final List abilities = getAdditionalCostSpell(original); - - final Card source = original.getHostCard(); - - if (!original.isSpell()) { - return abilities; - } - - // Buyback, Kicker - for (KeywordInterface inst : source.getKeywords()) { - final String keyword = inst.getOriginal(); - if (keyword.startsWith("Buyback")) { - for (int i = 0; i < abilities.size(); i++) { - final SpellAbility newSA = abilities.get(i).copy(); - newSA.setBasicSpell(false); - newSA.setPayCosts(new Cost(keyword.substring(8), false).add(newSA.getPayCosts())); - newSA.setDescription(newSA.getDescription() + " (with Buyback)"); - newSA.addOptionalCost(OptionalCost.Buyback); - if (newSA.canPlay()) { - abilities.add(i, newSA); - i++; - } - } - } else if (keyword.startsWith("MayFlashCost")) { - // this is there for the AI - if (source.getGame().getPhaseHandler().isPlayerTurn(source.getController())) { - continue; // don't cast it with additional flash cost during AI's own turn, commonly a waste of mana - } - final String[] k = keyword.split(":"); - for (int i = 0; i < abilities.size(); i++) { - final SpellAbility newSA = abilities.get(i).copy(); - newSA.setBasicSpell(false); - newSA.setPayCosts(new Cost(k[1], false).add(newSA.getPayCosts())); - newSA.setDescription(newSA.getDescription() + " (as though it had flash)"); - newSA.getRestrictions().setInstantSpeed(true); - if (newSA.canPlay()) { - abilities.add(i, newSA); - i++; - } - } - } else if (keyword.startsWith("Kicker")) { - String[] sCosts = TextUtil.split(keyword.substring(6), ':'); - boolean generic = "Generic".equals(sCosts[sCosts.length - 1]); - // If this is a "generic kicker" (Undergrowth), ignore value for kicker creations - int numKickers = sCosts.length - (generic ? 1 : 0); - for (int i = 0; i < abilities.size(); i++) { - int iUnKicked = i; - for (int j = 0; j < numKickers; j++) { - final SpellAbility newSA = abilities.get(iUnKicked).copy(); - newSA.setBasicSpell(false); - final Cost cost = new Cost(sCosts[j], false); - newSA.setPayCosts(cost.add(newSA.getPayCosts())); - if (!generic) { - newSA.setDescription(newSA.getDescription() + " (Kicker " + cost.toSimpleString() + ")"); - newSA.addOptionalCost(j == 0 ? OptionalCost.Kicker1 : OptionalCost.Kicker2); - } else { - newSA.setDescription(newSA.getDescription() + " (Optional " + cost.toSimpleString() + ")"); - newSA.addOptionalCost(OptionalCost.Generic); - } - if (newSA.canPlay()) { - abilities.add(i, newSA); - i++; - iUnKicked++; - } - } - if (numKickers == 2) { // case for both kickers - it's hardcoded since they never have more than 2 kickers - final SpellAbility newSA = abilities.get(iUnKicked).copy(); - newSA.setBasicSpell(false); - final Cost cost1 = new Cost(sCosts[0], false); - final Cost cost2 = new Cost(sCosts[1], false); - newSA.setDescription(TextUtil.addSuffix(newSA.getDescription(), TextUtil.concatWithSpace(" (Both kickers:", cost1.toSimpleString(), "and", TextUtil.addSuffix(cost2.toSimpleString(), ")")))); - newSA.setPayCosts(cost2.add(cost1.add(newSA.getPayCosts()))); - newSA.addOptionalCost(OptionalCost.Kicker1); - newSA.addOptionalCost(OptionalCost.Kicker2); - if (newSA.canPlay()) { - abilities.add(i, newSA); - i++; - } - } - } - } - } - - if (source.hasKeyword(Keyword.CONSPIRE)) { - int amount = source.getAmountOfKeyword(Keyword.CONSPIRE); - for (int kwInstance = 1; kwInstance <= amount; kwInstance++) { - for (int i = 0; i < abilities.size(); i++) { - final SpellAbility newSA = abilities.get(i).copy(); - newSA.setBasicSpell(false); - final String conspireCost = "tapXType<2/Creature.SharesColorWith/untapped creature you control that shares a color with " + source.getName() + ">"; - newSA.setPayCosts(new Cost(conspireCost, false).add(newSA.getPayCosts())); - final String tag = kwInstance > 1 ? " (Conspire " + kwInstance + ")" : " (Conspire)"; - newSA.setDescription(newSA.getDescription() + tag); - newSA.addOptionalCost(OptionalCost.Conspire); - newSA.addConspireInstance(); - if (newSA.canPlay()) { - abilities.add(++i, newSA); - } - } - } - } - - if (source.hasKeyword(Keyword.JUMP_START)) { - for (int i = 0; i < abilities.size(); i++) { - final SpellAbility newSA = abilities.get(i).copy(); - newSA.setBasicSpell(false); - final String jumpstartCost = "Discard<1/Card>"; - newSA.setPayCosts(new Cost(jumpstartCost, false).add(newSA.getPayCosts())); - newSA.getRestrictions().setZone(ZoneType.Graveyard); - newSA.setDescription(newSA.getDescription() + " (Jump-start)"); - newSA.addOptionalCost(OptionalCost.Jumpstart); - if (newSA.canPlay()) { - abilities.add(i, newSA); - i++; - } - } - } - - return abilities; - } - private static boolean hasUrzaLands(final Player p) { final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield); return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine")))