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 = GameActionUtil.addExtraKeywordCost(sa);
if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) {
CharmEffect.makeChoices(sa);
}
@@ -208,7 +210,7 @@ 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))
return false;
@@ -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);

View File

@@ -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;
}
}

View File

@@ -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")))

View File

@@ -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];

View File

@@ -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<Integer> values, Player relatedPlayer);
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 {
Conspire("Conspire"),
Buyback("Buyback"),
Entwine("Entwine"),
Kicker1("Kicker"),

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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<SpellAbil
// The ability is added to stack HERE
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.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid;
@@ -695,4 +696,11 @@ public class PlayerControllerForTests extends PlayerController {
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
PT:3/5
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
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.)

View File

@@ -1,7 +1,7 @@
Name:Stream of Thought
ManaCost:U
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
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.)

View File

@@ -4,6 +4,6 @@ Types:Legendary Creature Goblin Shaman
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.
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
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);
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 (flippedToCast && !castFaceDown) {
source.turnFaceDown(true);
@@ -199,9 +197,8 @@ public class HumanPlay {
}
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);
}
else {
@@ -231,7 +228,7 @@ public class HumanPlay {
sa.setActivatingPlayer(player);
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);
}

View File

@@ -23,6 +23,7 @@ import forge.card.CardStateName;
import forge.card.CardType;
import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameActionUtil;
import forge.game.GameObject;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
@@ -53,13 +54,11 @@ import java.util.Map;
*/
public class HumanPlaySpellAbility {
private final PlayerControllerHuman controller;
private final SpellAbility ability;
private final CostPayment payment;
private SpellAbility ability;
public HumanPlaySpellAbility(final PlayerControllerHuman controller0, final SpellAbility ability0, final CostPayment payment0) {
public HumanPlaySpellAbility(final PlayerControllerHuman controller0, final SpellAbility ability0) {
controller = controller0;
ability = ability0;
payment = payment0;
}
public final boolean playAbility(final boolean mayChooseTargets, final boolean isFree, final boolean skipStack) {
@@ -116,6 +115,11 @@ public class HumanPlaySpellAbility {
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
if (manaTypeConversion) {
AbilityUtils.applyManaColorConversion(payment, MagicColor.Constant.ANY_TYPE_CONVERSION);
@@ -155,7 +159,7 @@ public class HumanPlaySpellAbility {
if (!prerequisitesMet) {
if (!ability.isTrigger()) {
rollbackAbility(fromZone, fromState, zonePosition);
rollbackAbility(fromZone, fromState, zonePosition, payment);
if (ability.getHostCard().isMadness()) {
// if a player failed to play madness cost, move the card to graveyard
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
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.CostPartMana;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix;
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?");
}
@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();
}
}