PlayEffect: support AltCosts (#4649)

This commit is contained in:
tool4ever
2024-02-09 14:10:41 +01:00
committed by GitHub
parent 41cc82e0a7
commit f45d17e368
25 changed files with 87 additions and 88 deletions

View File

@@ -1539,8 +1539,7 @@ public class AiController {
boolean mustRespond = false;
if (top != null) {
mustRespond = top.hasParam("AIRespondsToOwnAbility"); // Forced combos (currently defined for Sensei's Divining Top)
mustRespond |= top.isTrigger() && top.getTrigger().getKeyword() != null
&& top.getTrigger().getKeyword().getKeyword() == Keyword.EVOKE; // Evoke sacrifice trigger
mustRespond |= top.isTrigger() && top.getTrigger().isKeyword(Keyword.EVOKE); // Evoke sacrifice trigger
}
if (topOwnedByAI) {

View File

@@ -106,7 +106,7 @@ public class ComputerUtilAbility {
for (SpellAbility sa : originListWithAddCosts) {
// determine which alternative costs are cheaper than the original and prioritize them
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player);
List<SpellAbility> saAltCosts = GameActionUtil.getAlternativeCosts(sa, player, false);
List<SpellAbility> priorityAltSa = Lists.newArrayList();
List<SpellAbility> otherAltSa = Lists.newArrayList();
for (SpellAbility altSa : saAltCosts) {

View File

@@ -1231,7 +1231,7 @@ public class ComputerUtilCombat {
}
// Extra check for the Exalted trigger in case we're declaring more than one attacker
if (combat != null && trigger.getKeyword() != null && trigger.getKeyword().getKeyword() == Keyword.EXALTED) {
if (combat != null && trigger.isKeyword(Keyword.EXALTED)) {
if (!combat.getAttackers().isEmpty() && !combat.getAttackers().contains(attacker)) {
continue;
}

View File

@@ -4,7 +4,6 @@ import forge.ai.AiPlayDecision;
import forge.ai.ComputerUtil;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.card.CardStateName;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.player.Player;
@@ -45,7 +44,7 @@ public class DiscoverAi extends SpellAbilityAi {
@Override
public boolean confirmAction(Player ai, SpellAbility sa, PlayerActionConfirmMode mode, String message, Map<String, Object> params) {
Card c = (Card)params.get("Card");
for (SpellAbility s : AbilityUtils.getBasicSpellsFromPlayEffect(c, ai, CardStateName.Original)) { // TODO: other states for split cards and MDFC?
for (SpellAbility s : AbilityUtils.getBasicSpellsFromPlayEffect(c, ai)) {
if (s instanceof LandAbility) {
// return false or we get a ClassCastException later if the AI encounters MDFC with land backside
return false;

View File

@@ -153,7 +153,7 @@ public class PlayAi extends SpellAbilityAi {
public boolean apply(final Card c) {
// TODO needs to be aligned for MDFC along with getAbilityToPlay so the knowledge
// of which spell was the reason for the choice can be used there
for (SpellAbility s : AbilityUtils.getBasicSpellsFromPlayEffect(c, ai, state)) {
for (SpellAbility s : AbilityUtils.getSpellsFromPlayEffect(c, ai, state, false)) {
if (!sa.matchesValidParam("ValidSA", s)) {
continue;
}

View File

@@ -20,6 +20,7 @@ import forge.game.card.CardPredicates;
import forge.game.card.CardState;
import forge.game.card.CardView;
import forge.game.card.IHasCardView;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
@@ -145,6 +146,9 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
this.hostCard = c;
}
public boolean isKeyword(Keyword kw) {
return this.keyword != null && this.keyword.getKeyword() == kw;
}
public KeywordInterface getKeyword() {
return this.keyword;
}

View File

@@ -116,7 +116,7 @@ public class GameAction {
// Rule 111.8: A token that has left the battlefield can't move to another zone
if (!c.isSpell() && c.isToken() && !fromBattlefield && zoneFrom != null && !zoneFrom.is(ZoneType.Stack)
&& (cause == null || !(cause instanceof SpellPermanent) || !cause.hasSVar("IsCastFromPlayEffect"))) {
&& (cause == null || !(cause instanceof SpellPermanent) || !cause.isCastFromPlayEffect())) {
return c;
}

View File

@@ -82,7 +82,7 @@ public final class GameActionUtil {
* a possible alternative cost the provided activator can use to pay
* the provided {@link SpellAbility}.
*/
public static final List<SpellAbility> getAlternativeCosts(final SpellAbility sa, final Player activator) {
public static final List<SpellAbility> getAlternativeCosts(final SpellAbility sa, final Player activator, boolean altCostOnly) {
final List<SpellAbility> alternatives = Lists.newArrayList();
Card source = sa.getHostCard();
@@ -134,6 +134,9 @@ public final class GameActionUtil {
newSA.setBasicSpell(false);
changedManaCost = true;
} else {
if (altCostOnly) {
continue;
}
newSA = sa.copy(activator);
}

View File

@@ -2898,21 +2898,17 @@ public class AbilityUtils {
}
public static final List<SpellAbility> getBasicSpellsFromPlayEffect(final Card tgtCard, final Player controller) {
return getBasicSpellsFromPlayEffect(tgtCard, controller, CardStateName.Original);
return getSpellsFromPlayEffect(tgtCard, controller, CardStateName.Original, false);
}
public static final List<SpellAbility> getBasicSpellsFromPlayEffect(final Card tgtCard, final Player controller, CardStateName state) {
public static final List<SpellAbility> getSpellsFromPlayEffect(final Card tgtCard, final Player controller, CardStateName state, boolean withAltCost) {
List<SpellAbility> sas = new ArrayList<>();
List<SpellAbility> list = Lists.newArrayList(tgtCard.getBasicSpells());
List<SpellAbility> list = new ArrayList<>();
collectSpellsForPlayEffect(list, tgtCard.getState(tgtCard.getCurrentStateName()), controller, withAltCost);
CardState original = tgtCard.getState(state);
if (tgtCard.isFaceDown()) {
Iterables.addAll(list, tgtCard.getBasicSpells(original));
collectSpellsForPlayEffect(list, original, controller, withAltCost);
} else {
if (tgtCard.isLand()) {
LandAbility la = new LandAbility(tgtCard, controller, null);
la.setCardState(original);
list.add(la);
}
if (state == CardStateName.Transformed && tgtCard.isPermanent() && !tgtCard.isAura()) {
// casting defeated battle
Spell sp = new SpellPermanent(tgtCard, original);
@@ -2920,13 +2916,7 @@ public class AbilityUtils {
list.add(sp);
}
if (tgtCard.isModal()) {
CardState modal = tgtCard.getState(CardStateName.Modal);
Iterables.addAll(list, tgtCard.getBasicSpells(modal));
if (modal.getType().isLand()) {
LandAbility la = new LandAbility(tgtCard, controller, null);
la.setCardState(modal);
list.add(la);
}
collectSpellsForPlayEffect(list, tgtCard.getState(CardStateName.Modal), controller, withAltCost);
}
}
@@ -2949,6 +2939,7 @@ public class AbilityUtils {
if (res.checkTimingRestrictions(tgtCard, newSA)
// still need to check the other restrictions like Aftermath
&& res.checkOtherRestrictions(tgtCard, newSA, controller)) {
newSA.setCastFromPlayEffect(true);
sas.add(newSA);
}
}
@@ -2956,6 +2947,27 @@ public class AbilityUtils {
return sas;
}
private static void collectSpellsForPlayEffect(final List<SpellAbility> result, final CardState state, final Player controller, final boolean withAltCost) {
if (state.getType().isLand()) {
LandAbility la = new LandAbility(state.getCard(), controller, null);
la.setCardState(state);
result.add(la);
}
final Iterable<SpellAbility> spells = state.getSpellAbilities();
for (SpellAbility sa : spells) {
if (!sa.isSpell()) {
continue;
}
if (!withAltCost && !sa.isBasicSpell()) {
continue;
}
result.add(sa);
if (withAltCost) {
result.addAll(GameActionUtil.getAlternativeCosts(sa, controller, true));
}
}
}
public static final String applyAbilityTextChangeEffects(final String def, final CardTraitBase ability) {
if (ability == null || !ability.isIntrinsic() || ability.hasParam("LockInText")) {
return def;

View File

@@ -1058,7 +1058,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (!decider.getController().confirmAction(tgtSA, null, Localizer.getInstance().getMessage("lblDoYouWantPlayCard", CardTranslation.getTranslatedName(tgtCard.getName())), null)) {
continue;
}
tgtSA.setSVar("IsCastFromPlayEffect", "True");
// if played, that card cannot be found
if (decider.getController().playSaFromPlayEffect(tgtSA)) {
fetchList.remove(tgtCard);

View File

@@ -125,8 +125,6 @@ public class DiscoverEffect extends SpellAbilityEffect {
tgtSA.getTargetRestrictions().setMandatory(true);
}
tgtSA.setSVar("IsCastFromPlayEffect", "True");
if (p.getController().playSaFromPlayEffect(tgtSA)) {
final Card played = tgtSA.getHostCard();
// add remember successfully played here if ever needed

View File

@@ -91,6 +91,7 @@ public class PlayEffect extends SpellAbilityEffect {
final boolean imprint = sa.hasParam("ImprintPlayed");
final boolean forget = sa.hasParam("ForgetPlayed");
final boolean hasTotalCMCLimit = sa.hasParam("WithTotalCMC");
final boolean altCost = sa.hasParam("WithoutManaCost") || sa.hasParam("PlayCost");
int totalCMCLimit = Integer.MAX_VALUE;
final Player controller;
if (sa.hasParam("Controller")) {
@@ -298,9 +299,7 @@ public class PlayEffect extends SpellAbilityEffect {
state = CardStateName.Transformed;
}
// TODO if cost isn't replaced should include alternative ones
// get basic spells (no flashback, etc.)
List<SpellAbility> sas = AbilityUtils.getBasicSpellsFromPlayEffect(tgtCard, controller, state);
List<SpellAbility> sas = AbilityUtils.getSpellsFromPlayEffect(tgtCard, controller, state, !altCost);
if (sa.hasParam("ValidSA")) {
final String valid[] = sa.getParam("ValidSA").split(",");
sas.removeIf(sp -> !sp.isValid(valid, controller , source, sa));
@@ -367,8 +366,7 @@ public class PlayEffect extends SpellAbilityEffect {
final int tgtCMC = tgtSA.getPayCosts().getTotalMana().getCMC();
// illegal action, cancel early
if ((sa.hasParam("WithoutManaCost") || sa.hasParam("PlayCost")) && tgtSA.costHasManaX() &&
tgtSA.getPayCosts().getCostMana().getXMin() > 0) {
if (altCost && tgtSA.costHasManaX() && tgtSA.getPayCosts().getCostMana().getXMin() > 0) {
continue;
}
@@ -384,7 +382,7 @@ public class PlayEffect extends SpellAbilityEffect {
}
abCost = new Cost(source.getManaCost(), false);
} else if (cost.equals("SuspendCost")) {
abCost = Iterables.find(tgtCard.getNonManaAbilities(), s -> s.getKeyword() != null && s.getKeyword().getKeyword() == Keyword.SUSPEND).getPayCosts();
abCost = Iterables.find(tgtCard.getNonManaAbilities(), s -> s.isKeyword(Keyword.SUSPEND)).getPayCosts();
} else {
if (cost.contains("ConvertedManaCost")) {
if (unpayableCost) {
@@ -453,8 +451,6 @@ public class PlayEffect extends SpellAbilityEffect {
addIllusionaryMaskReplace(tgtCard, sa, moveParams);
}
tgtSA.setSVar("IsCastFromPlayEffect", "True");
// Add controlled by player to target SA so when the spell is resolving, the controller would be changed again
if (controlledByPlayer != null) {
tgtSA.setControlledByPlayer(controlledByTimeStamp, controlledByPlayer);

View File

@@ -7095,7 +7095,6 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
public List<SpellAbility> getAllPossibleAbilities(final Player player, final boolean removeUnplayable) {
CardState oState = getState(CardStateName.Original);
// this can only be called by the Human
final List<SpellAbility> abilities = Lists.newArrayList();
for (SpellAbility sa : getSpellAbilities()) {
//adventure spell check
@@ -7105,12 +7104,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
//add alternative costs as additional spell abilities
abilities.add(sa);
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
}
if (isFaceDown() && isInZone(ZoneType.Exile)) {
for (final SpellAbility sa : oState.getSpellAbilities()) {
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
}
}
// Add Modal Spells
@@ -7120,7 +7119,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
// only add Spells there
if (sa.isSpell()) {
abilities.add(sa);
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
}
}
}

View File

@@ -274,27 +274,6 @@ public class CardFactoryUtil {
return AbilityFactory.getAbility(ab, sourceCard);
}
/**
* <p>
* countOccurrences.
* </p>
*
* @param arg1
* a {@link java.lang.String} object.
* @param arg2
* a {@link java.lang.String} object.
* @return a int.
*/
public static int countOccurrences(final String arg1, final String arg2) {
int count = 0;
int index = 0;
while ((index = arg1.indexOf(arg2, index)) != -1) {
++index;
++count;
}
return count;
}
/**
* <p>
* parseMath.

View File

@@ -521,7 +521,7 @@ public class Combat {
* @param blocker the blocking creature.
*/
public void addBlockerToDamageAssignmentOrder(Card attacker, Card blocker) {
final CardCollection oldBlockers = blockersOrderedForDamageAssignment.get(attacker);
final CardCollection oldBlockers = blockersOrderedForDamageAssignment.get(attacker);
if (oldBlockers == null || oldBlockers.isEmpty()) {
blockersOrderedForDamageAssignment.put(attacker, new CardCollection(blocker));
} else {
@@ -815,24 +815,21 @@ public class Combat {
defender = getDefenderPlayerByAttacker(attacker);
}
assignCombatDamageToCreature = !attacker.getGame().getCombat().isBlocked(attacker) &&
getDefendersCreatures().size() > 0 &&
attacker.hasKeyword("If CARDNAME is unblocked, you may have it assign its combat damage to " +
"a creature defending player controls.") &&
assignCombatDamageToCreature = !attacker.getGame().getCombat().isBlocked(attacker) && getDefendersCreatures().size() > 0 &&
attacker.hasKeyword("If CARDNAME is unblocked, you may have it assign its combat damage to a creature defending player controls.") &&
assigningPlayer.getController().confirmStaticApplication(attacker, PlayerActionConfirmMode.AlternativeDamageAssignment,
Localizer.getInstance().getMessage("lblAssignCombatDamageToCreature",
CardTranslation.getTranslatedName(attacker.getName())), null);
if (divideCombatDamageAsChoose) {
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
orderedBlockers = getDefendersCreatures();
} else {
for (Card c : getDefendersCreatures()) {
if (!orderedBlockers.contains(c)) {
orderedBlockers.add(c);
}
}
Localizer.getInstance().getMessage("lblAssignCombatDamageToCreature", CardTranslation.getTranslatedName(attacker.getName())), null);
if (divideCombatDamageAsChoose) {
if (orderedBlockers == null || orderedBlockers.isEmpty()) {
orderedBlockers = getDefendersCreatures();
} else {
for (Card c : getDefendersCreatures()) {
if (!orderedBlockers.contains(c)) {
orderedBlockers.add(c);
}
}
}
}
}
assignedDamage = true;

View File

@@ -106,7 +106,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
// for uncastables like lotus bloom, check if manaCost is blank (except for morph spells)
// but ignore if it comes from PlayEffect
if (!isCastFaceDown()
&& !hasSVar("IsCastFromPlayEffect")
&& !isCastFromPlayEffect()
&& isBasicSpell()
&& origCost.isNoCost()) {
return false;

View File

@@ -166,6 +166,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private boolean isCopied = false;
private boolean mayChooseNewTargets = false;
private boolean isCastFromPlayEffect = false;
private EnumSet<OptionalCost> optionalCosts = EnumSet.noneOf(OptionalCost.class);
private TargetRestrictions targetRestrictions;
private TargetChoices targetChosen = new TargetChoices();
@@ -1645,6 +1647,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
undoable = b;
}
public boolean isCastFromPlayEffect() {
return isCastFromPlayEffect;
}
public void setCastFromPlayEffect(boolean b) {
isCastFromPlayEffect = b;
}
public boolean isCopied() {
return isCopied;
}
@@ -2509,7 +2518,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (getRestrictions().isInstantSpeed()) {
return true;
}
if ((isSpell() || this instanceof LandAbility) && (hasSVar("IsCastFromPlayEffect") || host.isInstant() || host.hasKeyword(Keyword.FLASH))) {
if ((isSpell() || this instanceof LandAbility) && (isCastFromPlayEffect() || host.isInstant() || host.hasKeyword(Keyword.FLASH))) {
return true;
}

View File

@@ -35,6 +35,7 @@ import forge.game.card.CardLists;
import forge.game.card.CardPlayOption;
import forge.game.card.CardUtil;
import forge.game.cost.IndividualCostPaymentInstance;
import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.staticability.StaticAbilityCastWithFlash;
@@ -249,7 +250,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
}
if (sa.isSpell()) {
final CardPlayOption o = c.mayPlay(sa.getMayPlay());
if (o == null) {
if (o == null || sa.isCastFromPlayEffect()) {
return this.getZone() == null || (cardZone != null && cardZone.is(this.getZone()));
} else if (o.getPlayer() == activator) {
Map<String,String> params = sa.getMayPlay().getMapParams();
@@ -377,6 +378,10 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
return false;
}
if (sa.isKeyword(Keyword.FUSE) && !c.isInZone(ZoneType.Hand)) {
return false;
}
if (getCardsInHand() != -1) {
int h = activator.getCardsIn(ZoneType.Hand).size();
if (getCardsInHand2() != -1) {
@@ -596,7 +601,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables {
return false;
}
if (!sa.hasSVar("IsCastFromPlayEffect")) {
if (!sa.isCastFromPlayEffect()) {
if (!checkTimingRestrictions(c, sa)) {
return false;
}

View File

@@ -3,7 +3,7 @@ ManaCost:3 U
Types:Enchantment Saga
K:Chapter:3:DBReturn1,DBReturn2,DBTransform
SVar:DBReturn1:DB$ ChangeZone | ValidTgts$ Creature,Planeswalker | TargetMin$ 0 | TargetMax$ 1 | Origin$ Battlefield | Destination$ Hand | TgtPrompt$ Select target creature or planeswalker | SpellDescription$ Return up to one target creature or planeswalker to its owner's hand.
SVar:DBReturn2:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ChangeType$ Artifact | ChangeNum$ 1 | Mandatory$ True | RememberChanged$ True | SubAbility$ DBDraw | SpellDescription$ Return an artifact card from your graveyard to your hand. If you can't, draw a card.
SVar:DBReturn2:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ChangeType$ Artifact.YouOwn | Hidden$ True | ChangeNum$ 1 | Mandatory$ True | RememberChanged$ True | SubAbility$ DBDraw | SpellDescription$ Return an artifact card from your graveyard to your hand. If you can't, draw a card.
SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0 | SubAbility$ DBCleanup
SVar:DBTransform:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBReturn | SpellDescription$ Exile this Saga, then return it to the battlefield transformed under your control.
SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Transformed$ True | GainControl$ True | SubAbility$ DBCleanup

View File

@@ -2,7 +2,7 @@ Name:Karn, the Great Creator
ManaCost:4
Types:Legendary Planeswalker Karn
Loyalty:5
S:Mode$ CantBeActivated | Activator$ Opponent | AffectedZone$ Battlefield | ValidCard$ Artifact | ValidSA$ Activated | Description$ Activated abilities of artifacts your opponents control can't be activated.
S:Mode$ CantBeActivated | AffectedZone$ Battlefield | ValidCard$ Artifact.OppCtrl | ValidSA$ Activated | Description$ Activated abilities of artifacts your opponents control can't be activated.
SVar:NonStackingEffect:True
A:AB$ Animate | Cost$ AddCounter<1/LOYALTY> | TargetMin$ 0 | TargetMax$ 1 | Planeswalker$ True | ValidTgts$ Artifact.nonCreature | TgtPrompt$ Select target noncreature artifact | Power$ X | Toughness$ X | Types$ Artifact,Creature | Duration$ UntilYourNextTurn | AILogic$ PTByCMC | SpellDescription$ Until your next turn, up to one target noncreature artifact becomes an artifact creature with power and toughness each equal to its mana value.
SVar:X:Targeted$CardManaCost

View File

@@ -3,6 +3,6 @@ ManaCost:2 W W
Types:Legendary Creature Angel
PT:3/4
K:Flying
S:Mode$ CantBeActivated | Activator$ Opponent | AffectedZone$ Battlefield | ValidCard$ Creature | ValidSA$ Activated | Description$ Activated abilities of creatures your opponents control can't be activated.
S:Mode$ CantBeActivated | AffectedZone$ Battlefield | ValidCard$ Creature.OppCtrl | ValidSA$ Activated | Description$ Activated abilities of creatures your opponents control can't be activated.
SVar:PlayMain1:TRUE
Oracle:Flying\nActivated abilities of creatures your opponents control can't be activated.

View File

@@ -2,7 +2,7 @@ Name:Press the Enemy
ManaCost:2 U U
Types:Instant
A:SP$ ChangeZone | ValidTgts$ Permanent.nonLand+OppCtrl,Card.inZoneStack+OppCtrl | TgtZone$ Stack,Battlefield | Origin$ Battlefield,Stack | Fizzle$ True | Destination$ Hand | SubAbility$ DBMayPlay | SpellDescription$ Return target spell or nonland permanent an opponent controls to its owner's hand.
SVar:DBMayPlay:DB$ Play | Valid$ Card.YouOwn | ValidZone$ Hand| ValidSA$ Instant.cmcLEZ,Sorcery.cmcLEZ | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup | SpellDescription$ You may cast an instant or sorcery spell with equal or lesser mana value from your hand without paying its mana cost.
SVar:DBMayPlay:DB$ Play | Valid$ Card.YouOwn | ValidZone$ Hand | ValidSA$ Instant.cmcLEZ,Sorcery.cmcLEZ | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup | SpellDescription$ You may cast an instant or sorcery spell with equal or lesser mana value from your hand without paying its mana cost.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
DeckHints:Type$Sorcery|Instant
SVar:X:SpellTargeted$CardManaCostLKI

View File

@@ -116,7 +116,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
List<SpellAbility> result = Lists.newArrayList();
for (SpellAbility sa : card.getManaAbilities()) {
result.add(sa);
result.addAll(GameActionUtil.getAlternativeCosts(sa, player));
result.addAll(GameActionUtil.getAlternativeCosts(sa, player, false));
}
final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(result.size());
for (final SpellAbility sa : result) {

View File

@@ -851,7 +851,7 @@ public class HumanCostDecision extends CostDecisionMakerBase {
return PaymentDecision.number(0);
}
// player might not want to pay if from a trigger
if (!ability.hasSVar("IsCastFromPlayEffect") && hand.size() == num) {
if (!ability.isCastFromPlayEffect() && hand.size() == num) {
return PaymentDecision.card(hand);
}

View File

@@ -2910,7 +2910,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
// Human player is choosing targets for an ability
// controlled by chosen player.
sa.setActivatingPlayer(p);
sa.setSVar("IsCastFromPlayEffect", "True");
sa.setCastFromPlayEffect(true);
HumanPlay.playSaWithoutPayingManaCost(PlayerControllerHuman.this, getGame(), sa, true);
}
// playSa could fire some triggers