SpellAbility: do xPaid inside spellAbility

This commit is contained in:
Hans Mackowiak
2020-04-12 15:19:04 +00:00
committed by Michael Kamensky
parent 9b9f88129f
commit 0d1105377e
16 changed files with 129 additions and 39 deletions

View File

@@ -1169,7 +1169,7 @@ public class ComputerUtilMana {
cost.increaseShard(shardToGrow, manaToAdd);
if (!test) {
card.setXManaCostPaid(manaToAdd / cost.getXcounter());
sa.setXManaCostPaid(manaToAdd / cost.getXcounter());
}
}

View File

@@ -157,11 +157,6 @@ public class GameAction {
c.removeSVar("EndOfTurnLeavePlay");
}
// Clean up temporary variables such as Sunburst value or announced PayX value
if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) {
c.clearTemporaryVars();
}
if (fromBattlefield && !toBattlefield) {
c.getController().setRevolt(true);
}
@@ -173,7 +168,7 @@ public class GameAction {
// if to Battlefield and it is caused by an replacement effect,
// try to get previous LKI if able
if (zoneTo.is(ZoneType.Battlefield)) {
if (toBattlefield) {
if (cause != null && cause.isReplacementAbility()) {
ReplacementEffect re = cause.getReplacementEffect();
if (ReplacementType.Moved.equals(re.getMode())) {
@@ -244,6 +239,12 @@ public class GameAction {
}
}
// Clean up temporary variables such as Sunburst value or announced PayX value
if (!(zoneTo.is(ZoneType.Stack) || zoneTo.is(ZoneType.Battlefield))) {
copied.clearTemporaryVars();
}
if (!suppress) {
if (zoneFrom == null) {
copied.getOwner().addInboundToken(copied);

View File

@@ -25,6 +25,8 @@ import forge.game.player.Player;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.spellability.*;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerType;
import forge.game.zone.ZoneType;
import forge.util.Expressions;
import forge.util.TextUtil;
@@ -1573,11 +1575,47 @@ public class AbilityUtils {
// special logic for xPaid in SpellAbility
if (sq[0].contains("xPaid")) {
// ETB effects of cloned cards have xPaid = 0
if (sa.hasParam("ETB") && sa.getOriginalHost() != null) {
SpellAbility root = sa.getRootAbility();
// 107.3i If an object gains an ability, the value of X within that ability is the value defined by that ability,
// or 0 if that ability doesnt define a value of X. This is an exception to rule 107.3h. This may occur with ability-adding effects, text-changing effects, or copy effects.
if (root.getXManaCostPaid() != null) {
return CardFactoryUtil.doXMath(root.getXManaCostPaid(), expr, c);
}
// If the chosen creature has X in its mana cost, that X is considered to be 0.
// The value of X in Altered Egos last ability will be whatever value was chosen for X while casting Altered Ego.
if (sa.getOriginalHost() != null || !sa.getHostCard().equals(c)) {
return 0;
}
return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
if (root.isTrigger()) {
Trigger t = root.getTrigger();
// 107.3k If an objects enters-the-battlefield triggered ability or replacement effect refers to X,
// and the spell that became that object as it resolved had a value of X chosen for any of its costs,
// the value of X for that ability is the same as the value of X for that spell, although the value of X for that permanent is 0.
if (TriggerType.ChangesZone.equals(t.getMode())
&& ZoneType.Battlefield.name().equals(t.getParam("Destination"))) {
return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
} else if (TriggerType.SpellCast.equals(t.getMode())) {
// Cast Trigger like Hydroid Krasis
return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
} else if (TriggerType.Cycled.equals(t.getMode())) {
SpellAbility cycleSA = (SpellAbility) sa.getTriggeringObject(AbilityKey.Cause);
if (cycleSA == null) {
return 0;
}
return CardFactoryUtil.doXMath(cycleSA.getXManaCostPaid(), expr, c);
}
}
if (root.isReplacementAbility()) {
if (sa.hasParam("ETB")) {
return CardFactoryUtil.doXMath(c.getXManaCostPaid(), expr, c);
}
}
return 0;
}
// Count$Kicked.<numHB>.<numNotHB>

View File

@@ -112,9 +112,8 @@ public abstract class SpellAbilityEffect {
sb.append(" ");
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace(svar,"=",String.valueOf(amount))));
} else{
if (sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null &&
sa.getPayCosts().getCostMana().getAmountOfX() > 0) {
int amount = sa.getHostCard().getXManaCostPaid();
if (sa.costHasManaX()) {
int amount = sa.getXManaCostPaid() == null ? 0 : sa.getXManaCostPaid();
sb.append(" ");
sb.append(TextUtil.enclosedParen(TextUtil.concatNoSpace("X","=",String.valueOf(amount))));
}

View File

@@ -222,7 +222,6 @@ public class Card extends GameEntity implements Comparable<Card> {
private int turnInZone;
private int xManaCostPaid = 0;
private Map<String, Integer> xManaCostPaidByColor;
private int sunburstValue = 0;
@@ -1089,10 +1088,11 @@ public class Card extends GameEntity implements Comparable<Card> {
}
public final int getXManaCostPaid() {
return xManaCostPaid;
}
public final void setXManaCostPaid(final int n) {
xManaCostPaid = n;
if (getCastSA() != null) {
Integer paid = getCastSA().getXManaCostPaid();
return paid == null ? 0 : paid;
}
return 0;
}
public final Map<String, Integer> getXManaCostPaidByColor() {
@@ -6430,7 +6430,6 @@ public class Card extends GameEntity implements Comparable<Card> {
removeSVar("PayX"); // Temporary AI X announcement variable
removeSVar("IsCastFromPlayEffect"); // Temporary SVar indicating that the spell is cast indirectly via AF Play
setSunburstValue(0); // Sunburst
setXManaCostPaid(0);
setXManaCostPaidByColor(null);
setKickerMagnitude(0);
setPseudoMultiKickerMagnitude(0);

View File

@@ -185,18 +185,12 @@ public class CardFactory {
c.setCopiedSpell(true);
if (bCopyDetails) {
c.setXManaCostPaid(original.getXManaCostPaid());
c.setXManaCostPaidByColor(original.getXManaCostPaidByColor());
c.setKickerMagnitude(original.getKickerMagnitude());
// Rule 706.10 : Madness is copied
if (original.isInZone(ZoneType.Stack)) {
c.setMadness(original.isMadness());
final SpellAbilityStackInstance si = controller.getGame().getStack().getInstanceFromSpellAbility(sa);
if (si != null) {
c.setXManaCostPaid(si.getXManaPaid());
}
}
for (OptionalCost cost : original.getOptionalCostsPaid()) {
@@ -875,6 +869,13 @@ public class CardFactory {
}
}
for (final Trigger trigger : state.getTriggers()) {
final SpellAbility newSa = trigger.getOverridingAbility();
if (newSa != null && newSa.getOriginalHost() == null) {
newSa.setOriginalHost(in);
}
}
if (sa.hasParam("GainTextOf") && originalState != null) {
state.setSetCode(originalState.getSetCode());
state.setRarity(originalState.getRarity());

View File

@@ -122,8 +122,10 @@ public abstract class CostPartWithList extends CostPart {
// always returns true, made this to inline with return
public boolean executePayment(SpellAbility ability, CardCollectionView targetCards) {
if (canPayListAtOnce()) { // This is used by reveal. Without it when opponent would reveal hand, you'll get N message boxes.
lkiList.addAll(targetCards);
if (canPayListAtOnce()) { // This is used by reveal. Without it when opponent would reveal hand, you'll get N message boxes.
for (Card c: targetCards) {
lkiList.add(CardUtil.getLKICopy(c));
}
cardList.addAll(doListPayment(ability, targetCards));
handleChangeZoneTrigger(ability);
return true;

View File

@@ -130,6 +130,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private final List<Mana> payingMana = Lists.newArrayList();
private final List<SpellAbility> paidAbilities = Lists.newArrayList();
private Integer xManaCostPaid = null;
private HashMap<String, CardCollection> paidLists = Maps.newHashMap();
@@ -447,6 +448,23 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
payCosts = abCost;
}
public boolean costHasX() {
if (getPayCosts() == null) {
return false;
}
return getPayCosts().hasXInAnyCostPart();
}
public boolean costHasManaX() {
if (getPayCosts() == null) {
return false;
}
if (getPayCosts().hasNoManaCost()) {
return false;
}
return getPayCosts().getCostMana().getAmountOfX() > 0;
}
public SpellAbilityRestriction getRestrictions() {
return restrictions;
}
@@ -1963,4 +1981,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public void setAlternativeCost(AlternativeCost ac) {
altCost = ac;
}
public Integer getXManaCostPaid() {
return xManaCostPaid;
}
public void setXManaCostPaid(final Integer n) {
xManaCostPaid = n;
}
}

View File

@@ -83,7 +83,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
// private ArrayList<Mana> payingMana = new ArrayList<Mana>();
// private ArrayList<AbilityMana> paidAbilities = new
// ArrayList<AbilityMana>();
private int xManaPaid = 0;
private Integer xManaPaid = null;
// Other Paid things
private final HashMap<String, CardCollection> paidHash;
@@ -116,7 +116,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
splicedCards = sa.getSplicedCards();
// TODO getXManaCostPaid should be on the SA, not the Card
xManaPaid = sa.getHostCard().getXManaCostPaid();
xManaPaid = sa.getXManaCostPaid();
// Triggering info
triggeringObjects = sa.getTriggeringObjects();
@@ -200,7 +200,7 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView {
// Set Cost specific things here
ability.setPaidHash(paidHash);
ability.setSplicedCards(splicedCards);
ability.getHostCard().setXManaCostPaid(xManaPaid);
ability.setXManaCostPaid(xManaPaid);
// Triggered
ability.setTriggeringObjects(triggeringObjects);

View File

@@ -53,7 +53,7 @@ public class TriggerCycled extends Trigger {
/** {@inheritDoc} */
@Override
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> runParams) {
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card);
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.Cause);
}
@Override

View File

@@ -556,4 +556,11 @@ public class WrappedAbility extends Ability {
public void setAlternativeCost(AlternativeCost ac) {
sa.setAlternativeCost(ac);
}
public Integer getXManaCostPaid() {
return sa.getXManaCostPaid();
}
public void setXManaCostPaid(final Integer n) {
sa.setXManaCostPaid(n);
}
}

View File

@@ -314,9 +314,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
// Run Cycled triggers
if (sp.isCycling()) {
runParams.clear();
runParams.put(AbilityKey.Card, sp.getHostCard());
game.getTriggerHandler().runTrigger(TriggerType.Cycled, runParams, false);
Map<AbilityKey, Object> cycleParams = AbilityKey.mapFromCard(sp.getHostCard());
cycleParams.put(AbilityKey.Cause, sp);
game.getTriggerHandler().runTrigger(TriggerType.Cycled, cycleParams, false);
}
if (sp.hasParam("Crew")) {

View File

@@ -0,0 +1,11 @@
Name:Shark Typhoon
ManaCost:5 U
Types:Enchantment
T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a noncreature spell, create an X/X blue Shark creature token with flying, where X is that spell's converted mana cost.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_x_x_shark_flying | TokenOwner$ You | LegacyImage$ u x x shark flying iko | TokenPower$ Y | TokenToughness$ Y | References$ Y | TokenAmount$ 1
SVar:Y:TriggeredCard$CardManaCost
K:Cycling:X 1 U
T:Mode$ Cycled | ValidCard$ Card.Self | Execute$ TrigToken2 | TriggerZones$ Hand | TriggerDescription$ When you cycle CARDNAME, create an X/X blue Shark creature token with flying.
SVar:TrigToken2:DB$ Token | TokenAmount$ 1 | TokenScript$ u_x_x_shark_flying | TokenOwner$ You | LegacyImage$ u x x shark flying iko | TokenPower$ X | TokenToughness$ X | References$ X | TokenAmount$ 1
SVar:X:Count$xPaid
Oracle:Whenever you cast a noncreature spell, create an X/X blue Shark creature token with flying, where X is that spell's converted mana cost.\nCycling {X}{1}{U} ({X}{1}{U}, Discard this card: Draw a card.)\nWhen you cycle Shark Typhoon, create an X/X blue Shark creature token with flying.

View File

@@ -0,0 +1,7 @@
Name:Shark
ManaCost:no cost
Types:Creature Shark
Colors:blue
PT:*/*
K:Flying
Oracle:Flying

View File

@@ -748,10 +748,10 @@ public class HumanPlay {
if (mc.getAmountOfX() > 0 && !"Count$xPaid".equals(xInCard)) { // announce X will overwrite whatever was in card script
int xPaid = AbilityUtils.calculateAmount(source, "X", ability);
toPay.setXManaCostPaid(xPaid, ability.getParam("XColor"));
source.setXManaCostPaid(xPaid);
ability.setXManaCostPaid(xPaid);
}
else if (source.getXManaCostPaid() > 0) { //ensure pre-announced X value retained
toPay.setXManaCostPaid(source.getXManaCostPaid(), ability.getParam("XColor"));
else if (ability.getXManaCostPaid() != null) { //ensure pre-announced X value retained
toPay.setXManaCostPaid(ability.getXManaCostPaid(), ability.getParam("XColor"));
}
int timesMultikicked = source.getKickerMagnitude();

View File

@@ -314,10 +314,10 @@ public class HumanPlaySpellAbility {
if (value == null) {
return false;
}
card.setXManaCostPaid(value);
ability.setXManaCostPaid(value);
}
} else if (manaCost.getMana().isZero() && ability.isSpell()) {
card.setXManaCostPaid(0);
ability.setXManaCostPaid(0);
}
}
return true;