diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index c775ed2e00c..89dfbcc4252 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -99,6 +99,8 @@ public class ComputerUtil { sa.resetPaidHash(); } + sa = GameActionUtil.addExtraKeywordCost(sa); + if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { CharmEffect.makeChoices(sa); } @@ -208,9 +210,9 @@ public class ComputerUtil { } // this is used for AI's counterspells - public static final boolean playStack(final SpellAbility sa, final Player ai, final Game game) { + public static final boolean playStack(SpellAbility sa, final Player ai, final Game game) { sa.setActivatingPlayer(ai); - if (!ComputerUtilCost.canPayCost(sa, ai)) + if (!ComputerUtilCost.canPayCost(sa, ai)) return false; final Card source = sa.getHostCard(); @@ -220,6 +222,9 @@ public class ComputerUtil { sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setHostCard(game.getAction().moveToStack(source, sa)); } + + sa = GameActionUtil.addExtraKeywordCost(sa); + final Cost cost = sa.getPayCosts(); if (cost == null) { ComputerUtilMana.payManaCost(ai, sa); @@ -249,13 +254,15 @@ public class ComputerUtil { } public static final boolean playSpellAbilityWithoutPayingManaCost(final Player ai, final SpellAbility sa, final Game game) { - final SpellAbility newSA = sa.copyWithNoManaCost(); + SpellAbility newSA = sa.copyWithNoManaCost(); newSA.setActivatingPlayer(ai); if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) { return false; } + newSA = GameActionUtil.addExtraKeywordCost(newSA); + final Card source = newSA.getHostCard(); if (newSA.isSpell() && !source.isCopiedSpell()) { source.setCastSA(newSA); @@ -275,7 +282,7 @@ public class ComputerUtil { return true; } - public static final void playNoStack(final Player ai, final SpellAbility sa, final Game game) { + public static final void playNoStack(final Player ai, SpellAbility sa, final Game game) { sa.setActivatingPlayer(ai); // TODO: We should really restrict what doesn't use the Stack if (ComputerUtilCost.canPayCost(sa, ai)) { @@ -287,6 +294,8 @@ public class ComputerUtil { sa.setHostCard(game.getAction().moveToStack(source, sa)); } + sa = GameActionUtil.addExtraKeywordCost(sa); + final Cost cost = sa.getPayCosts(); if (cost == null) { ComputerUtilMana.payManaCost(ai, sa); diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index df01caaf238..ab9be7a2d7c 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -25,6 +25,7 @@ import forge.game.card.*; import forge.game.card.CardPredicates.Presets; import forge.game.combat.Combat; import forge.game.cost.*; +import forge.game.keyword.KeywordInterface; import forge.game.mana.Mana; import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaCostBeingPaid; @@ -1253,4 +1254,25 @@ public class PlayerControllerAi extends PlayerController { // Always true? return true; } + + @Override + public int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt, + int max) { + // TODO: improve the logic depending on the keyword and the playability of the cost-modified SA (enough targets present etc.) + int chosenAmount = 0; + + Cost costSoFar = sa.getPayCosts() != null ? sa.getPayCosts().copy() : Cost.Zero; + + for (int i = 0; i < max; i++) { + costSoFar.add(cost); + SpellAbility fullCostSa = sa.copyWithDefinedCost(costSoFar); + if (ComputerUtilCost.canPayCost(fullCostSa, player)) { + chosenAmount++; + } else { + break; + } + } + + return chosenAmount; + } } diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index c655377090c..4c0d1e54542 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -29,7 +29,9 @@ import forge.game.card.CardPlayOption.PayManaCost; import forge.game.cost.Cost; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; +import forge.game.player.PlayerController; import forge.game.spellability.*; +import forge.game.trigger.Trigger; import forge.game.zone.ZoneType; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -250,11 +252,6 @@ public final class GameActionUtil { if (keyword.startsWith("Buyback")) { final Cost cost = new Cost(keyword.substring(8), false); costs.add(new OptionalCostValue(OptionalCost.Buyback, cost)); - } else if (keyword.equals("Conspire")) { - final String conspireCost = "tapXType<2/Creature.SharesColorWith/" + - "untapped creature you control that shares a color with " + source.getName() + ">"; - final Cost cost = new Cost(conspireCost, false); - costs.add(new OptionalCostValue(OptionalCost.Conspire, cost)); } else if (keyword.startsWith("Entwine")) { String[] k = keyword.split(":"); final Cost cost = new Cost(k[1], false); @@ -301,15 +298,11 @@ public final class GameActionUtil { } final SpellAbility result = sa.copy(); for (OptionalCostValue v : list) { - // need to copy cost, otherwise it does alter the original - result.setPayCosts(result.getPayCosts().copy().add(v.getCost())); + result.getPayCosts().add(v.getCost()); result.addOptionalCost(v.getType()); // add some extra logic, try to move it to other parts switch (v.getType()) { - case Conspire: - result.addConspireInstance(); - break; case Retrace: case Jumpstart: result.getRestrictions().setZone(ZoneType.Graveyard); @@ -363,6 +356,71 @@ public final class GameActionUtil { return abilities; } + public static SpellAbility addExtraKeywordCost(final SpellAbility sa) { + if (!sa.isSpell() || sa.isCopied()) { + return sa; + } + SpellAbility result = null; + final Card host = sa.getHostCard(); + final PlayerController pc = sa.getActivatingPlayer().getController(); + + host.getGame().getAction().checkStaticAbilities(false); + + boolean reset = false; + + for (KeywordInterface ki : host.getKeywords()) { + final String o = ki.getOriginal(); + if (o.equals("Conspire")) { + Trigger tr = Iterables.getFirst(ki.getTriggers(), null); + if (tr != null) { + final String conspireCost = "tapXType<2/Creature.SharesColorWith/" + + "untapped creature you control that shares a color with " + host.getName() + ">"; + final Cost cost = new Cost(conspireCost, false); + String str = "Pay for Conspire? " + cost.toSimpleString(); + + boolean v = pc.addKeywordCost(sa, cost, ki, str); + tr.setSVar("Conspire", v ? "1" : "0"); + + if (v) { + if (result == null) { + result = sa.copy(); + } + result.getPayCosts().add(cost); + reset = true; + } + } + } else if (o.startsWith("Replicate")) { + Trigger tr = Iterables.getFirst(ki.getTriggers(), null); + if (tr != null) { + String costStr = o.split(":")[1]; + final Cost cost = new Cost(costStr, false); + + String str = "Choose Amount for Replicate: " + cost.toSimpleString(); + + int v = pc.chooseNumberForKeywordCost(sa, cost, ki, str, Integer.MAX_VALUE); + + tr.setSVar("ReplicateAmount", String.valueOf(v)); + tr.getOverridingAbility().setSVar("ReplicateAmount", String.valueOf(v)); + + for (int i = 0; i < v; i++) { + if (result == null) { + result = sa.copy(); + } + result.getPayCosts().add(cost); + reset = true; + } + } + } + } + + // reset active Trigger + if (reset) { + host.getGame().getTriggerHandler().resetActiveTriggers(false); + } + + return result != null ? result : sa; + } + 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"))) diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index a6e70c9eae6..a53d25c23ad 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -2361,11 +2361,12 @@ public class CardFactoryUtil { inst.addTrigger(parsedTrigger); inst.addTrigger(parsedTrigReturn); } else if (keyword.equals("Conspire")) { - final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | Conspire$ True | Secondary$ True | TriggerDescription$ Copy CARDNAME if its conspire cost was paid"; + final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ Conspire | Secondary$ True | TriggerDescription$ Copy CARDNAME if its conspire cost was paid"; final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ 1"; final Trigger conspireTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic); conspireTrigger.setOverridingAbility(AbilityFactory.getAbility(abString, card)); + conspireTrigger.setSVar("Conspire", "0"); inst.addTrigger(conspireTrigger); } else if (keyword.startsWith("Cumulative upkeep")) { final String[] k = keyword.split(":"); @@ -2942,6 +2943,17 @@ public class CardFactoryUtil { + " exile CARDNAME. | Secondary$ True"; final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); inst.addTrigger(myTrigger); + } else if (keyword.startsWith("Replicate")) { + final String trigScript = "Mode$ SpellCast | ValidCard$ Card.Self | CheckSVar$ ReplicateAmount | Secondary$ True | TriggerDescription$ Copy CARDNAME for each time you paid its replicate cost"; + final String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ ReplicateAmount"; + + final Trigger replicateTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic); + final SpellAbility replicateAbility = AbilityFactory.getAbility(abString, card); + replicateAbility.setSVar("ReplicateAmount", "0"); + replicateTrigger.setOverridingAbility(replicateAbility); + replicateTrigger.setSVar("ReplicateAmount", "0"); + inst.addTrigger(replicateTrigger); + } else if (keyword.startsWith("Ripple")) { final String[] k = keyword.split(":"); final String num = k[1]; @@ -4225,9 +4237,6 @@ public class CardFactoryUtil { sa.setTemporary(!intrinsic); inst.addSpellAbility(sa); - - } else if (keyword.startsWith("Replicate")) { - card.getFirstSpellAbility().addAnnounceVar("Replicate"); } else if (keyword.startsWith("Scavenge")) { final String[] k = keyword.split(":"); final String manacost = k[1]; diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index fc0c09a2625..eb432ef4d5e 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -19,6 +19,7 @@ import forge.game.combat.Combat; import forge.game.cost.Cost; import forge.game.cost.CostPart; import forge.game.cost.CostPartMana; +import forge.game.keyword.KeywordInterface; import forge.game.mana.Mana; import forge.game.mana.ManaConversionMatrix; import forge.game.replacement.ReplacementEffect; @@ -47,7 +48,6 @@ public abstract class PlayerController { DeclareBlocker, Echo, Multikicker, - Replicate, CumulativeUpkeep, } @@ -182,6 +182,11 @@ public abstract class PlayerController { public abstract CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard); public abstract boolean payManaOptional(Card card, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose); + public abstract int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt, int max); + public boolean addKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt) { + return chooseNumberForKeywordCost(sa, cost, keyword, prompt, 1) == 1; + } + public abstract int chooseNumber(SpellAbility sa, String title, int min, int max); public abstract int chooseNumber(SpellAbility sa, String title, List values, Player relatedPlayer); public int chooseNumber(SpellAbility sa, String string, int min, int max, Map params) { diff --git a/forge-game/src/main/java/forge/game/spellability/OptionalCost.java b/forge-game/src/main/java/forge/game/spellability/OptionalCost.java index 64976a1670b..536edca813f 100644 --- a/forge-game/src/main/java/forge/game/spellability/OptionalCost.java +++ b/forge-game/src/main/java/forge/game/spellability/OptionalCost.java @@ -5,7 +5,6 @@ package forge.game.spellability; * */ public enum OptionalCost { - Conspire("Conspire"), Buyback("Buyback"), Entwine("Entwine"), Kicker1("Kicker"), diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 60c6426cc85..6b9045a22fd 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -144,7 +144,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private CardCollection tappedForConvoke = new CardCollection(); private Card sacrificedAsOffering = null; private Card sacrificedAsEmerge = null; - private int conspireInstances = 0; private AbilityManaPart manaPart = null; @@ -301,9 +300,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public boolean canPlayWithOptionalCost(OptionalCostValue opt) { - SpellAbility saCopy = this.copy(); - saCopy = GameActionUtil.addOptionalCosts(saCopy, Lists.newArrayList(opt)); - return saCopy.canPlay(); + return GameActionUtil.addOptionalCosts(this, Lists.newArrayList(opt)).canPlay(); } public boolean isPossible() { @@ -1700,19 +1697,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return ForgeScript.spellAbilityHasProperty(this, property, sourceController, source, spellAbility); } - // Methods enabling multiple instances of conspire - public void addConspireInstance() { - conspireInstances++; - } - - public void subtractConspireInstance() { - conspireInstances--; - } - - public int getConspireInstances() { - return conspireInstances; - } // End of Conspire methods - public boolean isCumulativeupkeep() { return cumulativeupkeep; } diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java b/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java index c516ebcb748..0dc88c7c789 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCast.java @@ -32,7 +32,6 @@ import forge.game.card.CardLists; import forge.game.card.CardUtil; import forge.game.cost.Cost; import forge.game.player.Player; -import forge.game.spellability.OptionalCost; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.TargetChoices; @@ -216,18 +215,6 @@ public class TriggerSpellAbilityCast extends Trigger { } } - if (hasParam("Conspire")) { - if (!spellAbility.isOptionalCostPaid(OptionalCost.Conspire)) { - return false; - } - if (spellAbility.getConspireInstances() == 0) { - return false; - } else { - spellAbility.subtractConspireInstance(); - //System.out.println("Conspire instances left = " + spellAbility.getConspireInstances()); - } - } - if (hasParam("Outlast")) { if (!spellAbility.isOutlast()) { return false; diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 0c3907380c9..df1ace5a315 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -38,7 +38,6 @@ import forge.card.mana.ManaCost; import forge.game.Game; import forge.game.GameLogEntryType; import forge.game.GameObject; -import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.Card; @@ -58,7 +57,6 @@ import forge.game.player.PlayerController.ManaPaymentPurpose; import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementLayer; -import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilityStatic; import forge.game.spellability.OptionalCost; import forge.game.spellability.Spell; @@ -319,35 +317,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable