Merge branch 'replicateConspire' into 'master'

Replicate rework

Closes #1068

See merge request core-developers/forge!1929
This commit is contained in:
Michael Kamensky
2019-07-08 09:52:20 +00:00
16 changed files with 163 additions and 97 deletions

View File

@@ -99,6 +99,8 @@ public class ComputerUtil {
sa.resetPaidHash(); sa.resetPaidHash();
} }
sa = GameActionUtil.addExtraKeywordCost(sa);
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa); CharmEffect.makeChoices(sa);
} }
@@ -208,9 +210,9 @@ public class ComputerUtil {
} }
// this is used for AI's counterspells // 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); sa.setActivatingPlayer(ai);
if (!ComputerUtilCost.canPayCost(sa, ai)) if (!ComputerUtilCost.canPayCost(sa, ai))
return false; return false;
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
@@ -220,6 +222,9 @@ public class ComputerUtil {
sa.setLastStateGraveyard(game.getLastStateGraveyard()); sa.setLastStateGraveyard(game.getLastStateGraveyard());
sa.setHostCard(game.getAction().moveToStack(source, sa)); sa.setHostCard(game.getAction().moveToStack(source, sa));
} }
sa = GameActionUtil.addExtraKeywordCost(sa);
final Cost cost = sa.getPayCosts(); final Cost cost = sa.getPayCosts();
if (cost == null) { if (cost == null) {
ComputerUtilMana.payManaCost(ai, sa); 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) { 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); newSA.setActivatingPlayer(ai);
if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) { if (!CostPayment.canPayAdditionalCosts(newSA.getPayCosts(), newSA)) {
return false; return false;
} }
newSA = GameActionUtil.addExtraKeywordCost(newSA);
final Card source = newSA.getHostCard(); final Card source = newSA.getHostCard();
if (newSA.isSpell() && !source.isCopiedSpell()) { if (newSA.isSpell() && !source.isCopiedSpell()) {
source.setCastSA(newSA); source.setCastSA(newSA);
@@ -275,7 +282,7 @@ public class ComputerUtil {
return true; 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); sa.setActivatingPlayer(ai);
// TODO: We should really restrict what doesn't use the Stack // TODO: We should really restrict what doesn't use the Stack
if (ComputerUtilCost.canPayCost(sa, ai)) { if (ComputerUtilCost.canPayCost(sa, ai)) {
@@ -287,6 +294,8 @@ public class ComputerUtil {
sa.setHostCard(game.getAction().moveToStack(source, sa)); sa.setHostCard(game.getAction().moveToStack(source, sa));
} }
sa = GameActionUtil.addExtraKeywordCost(sa);
final Cost cost = sa.getPayCosts(); final Cost cost = sa.getPayCosts();
if (cost == null) { if (cost == null) {
ComputerUtilMana.payManaCost(ai, sa); ComputerUtilMana.payManaCost(ai, sa);

View File

@@ -25,6 +25,7 @@ import forge.game.card.*;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.cost.*; import forge.game.cost.*;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana; import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
@@ -1253,4 +1254,25 @@ public class PlayerControllerAi extends PlayerController {
// Always true? // Always true?
return 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;
}
} }

View File

@@ -29,7 +29,9 @@ import forge.game.card.CardPlayOption.PayManaCost;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerController;
import forge.game.spellability.*; import forge.game.spellability.*;
import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.TextUtil; import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -250,11 +252,6 @@ public final class GameActionUtil {
if (keyword.startsWith("Buyback")) { if (keyword.startsWith("Buyback")) {
final Cost cost = new Cost(keyword.substring(8), false); final Cost cost = new Cost(keyword.substring(8), false);
costs.add(new OptionalCostValue(OptionalCost.Buyback, cost)); 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")) { } else if (keyword.startsWith("Entwine")) {
String[] k = keyword.split(":"); String[] k = keyword.split(":");
final Cost cost = new Cost(k[1], false); final Cost cost = new Cost(k[1], false);
@@ -301,15 +298,11 @@ public final class GameActionUtil {
} }
final SpellAbility result = sa.copy(); final SpellAbility result = sa.copy();
for (OptionalCostValue v : list) { for (OptionalCostValue v : list) {
// need to copy cost, otherwise it does alter the original result.getPayCosts().add(v.getCost());
result.setPayCosts(result.getPayCosts().copy().add(v.getCost()));
result.addOptionalCost(v.getType()); result.addOptionalCost(v.getType());
// add some extra logic, try to move it to other parts // add some extra logic, try to move it to other parts
switch (v.getType()) { switch (v.getType()) {
case Conspire:
result.addConspireInstance();
break;
case Retrace: case Retrace:
case Jumpstart: case Jumpstart:
result.getRestrictions().setZone(ZoneType.Graveyard); result.getRestrictions().setZone(ZoneType.Graveyard);
@@ -363,6 +356,71 @@ public final class GameActionUtil {
return abilities; 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) { private static boolean hasUrzaLands(final Player p) {
final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield); final CardCollectionView landsControlled = p.getCardsIn(ZoneType.Battlefield);
return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine"))) return Iterables.any(landsControlled, Predicates.and(CardPredicates.isType("Urza's"), CardPredicates.isType("Mine")))

View File

@@ -2361,11 +2361,12 @@ public class CardFactoryUtil {
inst.addTrigger(parsedTrigger); inst.addTrigger(parsedTrigger);
inst.addTrigger(parsedTrigReturn); inst.addTrigger(parsedTrigReturn);
} else if (keyword.equals("Conspire")) { } 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 String abString = "DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ 1";
final Trigger conspireTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic); final Trigger conspireTrigger = TriggerHandler.parseTrigger(trigScript, card, intrinsic);
conspireTrigger.setOverridingAbility(AbilityFactory.getAbility(abString, card)); conspireTrigger.setOverridingAbility(AbilityFactory.getAbility(abString, card));
conspireTrigger.setSVar("Conspire", "0");
inst.addTrigger(conspireTrigger); inst.addTrigger(conspireTrigger);
} else if (keyword.startsWith("Cumulative upkeep")) { } else if (keyword.startsWith("Cumulative upkeep")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
@@ -2942,6 +2943,17 @@ public class CardFactoryUtil {
+ " exile CARDNAME. | Secondary$ True"; + " exile CARDNAME. | Secondary$ True";
final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); final Trigger myTrigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
inst.addTrigger(myTrigger); 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")) { } else if (keyword.startsWith("Ripple")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
final String num = k[1]; final String num = k[1];
@@ -4225,9 +4237,6 @@ public class CardFactoryUtil {
sa.setTemporary(!intrinsic); sa.setTemporary(!intrinsic);
inst.addSpellAbility(sa); inst.addSpellAbility(sa);
} else if (keyword.startsWith("Replicate")) {
card.getFirstSpellAbility().addAnnounceVar("Replicate");
} else if (keyword.startsWith("Scavenge")) { } else if (keyword.startsWith("Scavenge")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
final String manacost = k[1]; final String manacost = k[1];

View File

@@ -19,6 +19,7 @@ import forge.game.combat.Combat;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.cost.CostPart; import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana; import forge.game.cost.CostPartMana;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana; import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaConversionMatrix;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
@@ -47,7 +48,6 @@ public abstract class PlayerController {
DeclareBlocker, DeclareBlocker,
Echo, Echo,
Multikicker, Multikicker,
Replicate,
CumulativeUpkeep, CumulativeUpkeep,
} }
@@ -182,6 +182,11 @@ public abstract class PlayerController {
public abstract CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard); public abstract CardCollection chooseCardsToDiscardToMaximumHandSize(int numDiscard);
public abstract boolean payManaOptional(Card card, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose); 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, int min, int max);
public abstract int chooseNumber(SpellAbility sa, String title, List<Integer> values, Player relatedPlayer); public abstract int chooseNumber(SpellAbility sa, String title, List<Integer> values, Player relatedPlayer);
public int chooseNumber(SpellAbility sa, String string, int min, int max, Map<String, Object> params) { public int chooseNumber(SpellAbility sa, String string, int min, int max, Map<String, Object> params) {

View File

@@ -5,7 +5,6 @@ package forge.game.spellability;
* *
*/ */
public enum OptionalCost { public enum OptionalCost {
Conspire("Conspire"),
Buyback("Buyback"), Buyback("Buyback"),
Entwine("Entwine"), Entwine("Entwine"),
Kicker1("Kicker"), Kicker1("Kicker"),

View File

@@ -144,7 +144,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private CardCollection tappedForConvoke = new CardCollection(); private CardCollection tappedForConvoke = new CardCollection();
private Card sacrificedAsOffering = null; private Card sacrificedAsOffering = null;
private Card sacrificedAsEmerge = null; private Card sacrificedAsEmerge = null;
private int conspireInstances = 0;
private AbilityManaPart manaPart = null; private AbilityManaPart manaPart = null;
@@ -301,9 +300,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
} }
public boolean canPlayWithOptionalCost(OptionalCostValue opt) { public boolean canPlayWithOptionalCost(OptionalCostValue opt) {
SpellAbility saCopy = this.copy(); return GameActionUtil.addOptionalCosts(this, Lists.newArrayList(opt)).canPlay();
saCopy = GameActionUtil.addOptionalCosts(saCopy, Lists.newArrayList(opt));
return saCopy.canPlay();
} }
public boolean isPossible() { public boolean isPossible() {
@@ -1700,19 +1697,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return ForgeScript.spellAbilityHasProperty(this, property, sourceController, source, spellAbility); 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() { public boolean isCumulativeupkeep() {
return cumulativeupkeep; return cumulativeupkeep;
} }

View File

@@ -32,7 +32,6 @@ import forge.game.card.CardLists;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices; 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 (hasParam("Outlast")) {
if (!spellAbility.isOutlast()) { if (!spellAbility.isOutlast()) {
return false; return false;

View File

@@ -38,7 +38,6 @@ import forge.card.mana.ManaCost;
import forge.game.Game; import forge.game.Game;
import forge.game.GameLogEntryType; import forge.game.GameLogEntryType;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
@@ -58,7 +57,6 @@ import forge.game.player.PlayerController.ManaPaymentPurpose;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.AbilityStatic; import forge.game.spellability.AbilityStatic;
import forge.game.spellability.OptionalCost; import forge.game.spellability.OptionalCost;
import forge.game.spellability.Spell; import forge.game.spellability.Spell;
@@ -319,35 +317,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
// The ability is added to stack HERE // The ability is added to stack HERE
si = push(sp); si = push(sp);
if (sp.isSpell() && (source.hasStartOfKeyword("Replicate")
|| ((source.isInstant() || source.isSorcery()) && Iterables.any(activator.getCardsIn(ZoneType.Battlefield),
CardPredicates.hasKeyword("Each instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost."))))) {
Integer magnitude = sp.getSVarInt("Replicate");
if (magnitude == null) {
magnitude = 0;
final Cost costReplicate = new Cost(source.getManaCost(), false);
boolean hasPaid = false;
int replicateCMC = source.getManaCost().getCMC();
do {
String prompt = TextUtil.concatWithSpace("Replicate for", source.toString(),"\r\nTimes Replicated:", magnitude.toString(),"\r\n");
hasPaid = activator.getController().payManaOptional(source, costReplicate, sp, prompt, ManaPaymentPurpose.Replicate);
if (hasPaid) {
magnitude++;
totManaSpent += replicateCMC;
}
} while (hasPaid);
}
// Replicate Trigger
String effect = TextUtil.concatWithSpace("DB$ CopySpellAbility | Cost$ 0 | Defined$ Parent | Amount$", magnitude.toString());
AbilitySub sa = (AbilitySub) AbilityFactory.getAbility(effect, source);
sa.setParent(sp);
sa.setDescription("Replicate - " + source);
sa.setTrigger(true);
sa.setCopied(true);
addSimultaneousStackEntry(sa);
}
} }
} }

View File

@@ -29,6 +29,7 @@ import forge.game.combat.CombatUtil;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.cost.CostPart; import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana; import forge.game.cost.CostPartMana;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana; import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid; import forge.game.mana.ManaCostBeingPaid;
@@ -695,4 +696,11 @@ public class PlayerControllerForTests extends PlayerController {
return false; return false;
} }
@Override
public int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt,
int max) {
// TODO Auto-generated method stub
return 0;
}
} }

View File

@@ -3,7 +3,7 @@ ManaCost:5 UR UR
Types:Creature Djinn Types:Creature Djinn
PT:3/5 PT:3/5
K:Flying K:Flying
K:Each instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost. S:Mode$ Continuous | AddKeyword$ Replicate:CardManaCost | Affected$ Instant.YouCtrl,Sorcery.YouCtrl | AffectedZone$ Stack | EffectZone$ Battlefield | Description$ Each instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost. (When you cast it, copy it for each time you paid its replicate cost. You may choose new targets for the copies.)
AI:RemoveDeck:All AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/djinn_illuminatus.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/djinn_illuminatus.jpg
Oracle:Flying\nEach instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost. (When you cast it, copy it for each time you paid its replicate cost. You may choose new targets for the copies.) Oracle:Flying\nEach instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost. (When you cast it, copy it for each time you paid its replicate cost. You may choose new targets for the copies.)

View File

@@ -1,7 +1,7 @@
Name:Stream of Thought Name:Stream of Thought
ManaCost:U ManaCost:U
Types:Sorcery Types:Sorcery
A:SP$ Mill | Cost$ U | NumCards$ 4 | ValidTgts$ Player | TgtPrompt$ Choose a player | RememberMilled$ True | SubAbility$ DBChangeZone | SpellDescription$ Target player puts the top four cards of their library into their graveyard. You shuffle up to four cards from your graveyard into your library.
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | DefinedPlayer$ You | Hidden$ True | ChangeNum$ 4 | ChangeType$ Card.YouOwn | Shuffle$ True
K:Replicate:2 U U K:Replicate:2 U U
A:SP$ Mill | Cost$ U | NumCards$ 4 | ValidTgts$ Player | TgtPrompt$ Choose a player | SubAbility$ DBChangeZone | SpellDescription$ Target player puts the top four cards of their library into their graveyard. You shuffle up to four cards from your graveyard into your library.
SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | DefinedPlayer$ You | Hidden$ True | ChangeNum$ 4 | ChangeType$ Card.YouOwn | Shuffle$ True
Oracle:Target player puts the top four cards of their library into their graveyard. You shuffle up to four cards from your graveyard into your library.\nReplicate {2}{U}{U} (When you cast this spell, copy it for each time you paid the replicate cost. You may choose new targets for the copies.) Oracle:Target player puts the top four cards of their library into their graveyard. You shuffle up to four cards from your graveyard into your library.\nReplicate {2}{U}{U} (When you cast this spell, copy it for each time you paid the replicate cost. You may choose new targets for the copies.)

View File

@@ -4,6 +4,6 @@ Types:Legendary Creature Goblin Shaman
PT:3/3 PT:3/3
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ WortETB | TriggerDescription$ When CARDNAME enters the battlefield, create two 1/1 red and green Goblin Warrior creature tokens. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ WortETB | TriggerDescription$ When CARDNAME enters the battlefield, create two 1/1 red and green Goblin Warrior creature tokens.
SVar:WortETB:DB$ Token | TokenAmount$ 2 | TokenScript$ rg_1_1_goblin_warrior | TokenOwner$ You SVar:WortETB:DB$ Token | TokenAmount$ 2 | TokenScript$ rg_1_1_goblin_warrior | TokenOwner$ You
S:Mode$ Continuous | AddKeyword$ Conspire | Affected$ Instant.Green+YouCtrl,Instant.Red+YouCtrl,Sorcery.Red+YouCtrl,Sorcery.Green+YouCtrl | AffectedZone$ Stack,Graveyard,Hand,Library,Exile | EffectZone$ Battlefield | Description$ Each red or green instant or sorcery spell you cast has conspire. (As you cast the spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose new targets for the copy.) S:Mode$ Continuous | AddKeyword$ Conspire | Affected$ Instant.Green+YouCtrl,Instant.Red+YouCtrl,Sorcery.Red+YouCtrl,Sorcery.Green+YouCtrl | AffectedZone$ Stack | EffectZone$ Battlefield | Description$ Each red or green instant or sorcery spell you cast has conspire. (As you cast the spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose new targets for the copy.)
SVar:Picture:http://www.wizards.com/global/images/magic/general/wort_the_raidmother.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/wort_the_raidmother.jpg
Oracle:When Wort, the Raidmother enters the battlefield, create two 1/1 red and green Goblin Warrior creature tokens.\nEach red or green instant or sorcery spell you cast has conspire. (As you cast the spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose new targets for the copy.) Oracle:When Wort, the Raidmother enters the battlefield, create two 1/1 red and green Goblin Warrior creature tokens.\nEach red or green instant or sorcery spell you cast has conspire. (As you cast the spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose new targets for the copy.)

View File

@@ -103,10 +103,8 @@ public class HumanPlay {
// System.out.println("Playing:" + sa.getDescription() + " of " + sa.getHostCard() + " new = " + newAbility); // System.out.println("Playing:" + sa.getDescription() + " of " + sa.getHostCard() + " new = " + newAbility);
if (newAbility) { if (newAbility) {
Cost abCost = sa.getPayCosts() == null ? new Cost("0", sa.isAbility()) : sa.getPayCosts();
CostPayment payment = new CostPayment(abCost, sa);
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa, payment); final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
if (!req.playAbility(true, false, false)) { if (!req.playAbility(true, false, false)) {
if (flippedToCast && !castFaceDown) { if (flippedToCast && !castFaceDown) {
source.turnFaceDown(true); source.turnFaceDown(true);
@@ -199,9 +197,8 @@ public class HumanPlay {
} }
sa = AbilityUtils.addSpliceEffects(sa); sa = AbilityUtils.addSpliceEffects(sa);
} }
final CostPayment payment = new CostPayment(sa.getPayCosts(), sa);
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa, payment); final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(mayChooseNewTargets, true, false); req.playAbility(mayChooseNewTargets, true, false);
} }
else { else {
@@ -231,7 +228,7 @@ public class HumanPlay {
sa.setActivatingPlayer(player); sa.setActivatingPlayer(player);
if (sa.getPayCosts() != null) { if (sa.getPayCosts() != null) {
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa, new CostPayment(sa.getPayCosts(), sa)); final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
req.playAbility(!useOldTargets, false, true); req.playAbility(!useOldTargets, false, true);
} }

View File

@@ -23,6 +23,7 @@ import forge.card.CardStateName;
import forge.card.CardType; import forge.card.CardType;
import forge.card.MagicColor; import forge.card.MagicColor;
import forge.game.Game; import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.Card; import forge.game.card.Card;
@@ -53,13 +54,11 @@ import java.util.Map;
*/ */
public class HumanPlaySpellAbility { public class HumanPlaySpellAbility {
private final PlayerControllerHuman controller; private final PlayerControllerHuman controller;
private final SpellAbility ability; private SpellAbility ability;
private final CostPayment payment;
public HumanPlaySpellAbility(final PlayerControllerHuman controller0, final SpellAbility ability0, final CostPayment payment0) { public HumanPlaySpellAbility(final PlayerControllerHuman controller0, final SpellAbility ability0) {
controller = controller0; controller = controller0;
ability = ability0; ability = ability0;
payment = payment0;
} }
public final boolean playAbility(final boolean mayChooseTargets, final boolean isFree, final boolean skipStack) { public final boolean playAbility(final boolean mayChooseTargets, final boolean isFree, final boolean skipStack) {
@@ -116,6 +115,11 @@ public class HumanPlaySpellAbility {
ability.resetPaidHash(); ability.resetPaidHash();
} }
ability = GameActionUtil.addExtraKeywordCost(ability);
Cost abCost = ability.getPayCosts() == null ? new Cost("0", ability.isAbility()) : ability.getPayCosts();
CostPayment payment = new CostPayment(abCost, ability);
// TODO Apply this to the SAStackInstance instead of the Player // TODO Apply this to the SAStackInstance instead of the Player
if (manaTypeConversion) { if (manaTypeConversion) {
AbilityUtils.applyManaColorConversion(payment, MagicColor.Constant.ANY_TYPE_CONVERSION); AbilityUtils.applyManaColorConversion(payment, MagicColor.Constant.ANY_TYPE_CONVERSION);
@@ -155,7 +159,7 @@ public class HumanPlaySpellAbility {
if (!prerequisitesMet) { if (!prerequisitesMet) {
if (!ability.isTrigger()) { if (!ability.isTrigger()) {
rollbackAbility(fromZone, fromState, zonePosition); rollbackAbility(fromZone, fromState, zonePosition, payment);
if (ability.getHostCard().isMadness()) { if (ability.getHostCard().isMadness()) {
// if a player failed to play madness cost, move the card to graveyard // if a player failed to play madness cost, move the card to graveyard
Card newCard = game.getAction().moveToGraveyard(c, null); Card newCard = game.getAction().moveToGraveyard(c, null);
@@ -240,7 +244,7 @@ public class HumanPlaySpellAbility {
} }
} }
private void rollbackAbility(final Zone fromZone, final CardStateName fromState, final int zonePosition) { private void rollbackAbility(final Zone fromZone, final CardStateName fromState, final int zonePosition, CostPayment payment) {
// cancel ability during target choosing // cancel ability during target choosing
final Game game = ability.getActivatingPlayer().getGame(); final Game game = ability.getActivatingPlayer().getGame();

View File

@@ -30,6 +30,7 @@ import forge.game.cost.Cost;
import forge.game.cost.CostPart; import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana; import forge.game.cost.CostPartMana;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana; import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaConversionMatrix;
import forge.game.player.*; import forge.game.player.*;
@@ -2930,5 +2931,19 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return InputConfirm.confirm(this, (SpellAbility)null, "Do you want to scry?"); return InputConfirm.confirm(this, (SpellAbility)null, "Do you want to scry?");
} }
@Override
public int chooseNumberForKeywordCost(SpellAbility sa, Cost cost, KeywordInterface keyword, String prompt,
int max) {
if (max <= 0) {
return 0;
}
if (max == 1) {
return InputConfirm.confirm(this, sa, prompt) ? 1 : 0;
}
Integer v = getGui().getInteger(prompt, 0, max, 9);
return v == null ? 0 : v.intValue();
}
} }