properly implement WaterbendXCost

This commit is contained in:
theelk801 2025-12-04 10:12:52 -05:00
parent f007ebb289
commit 4480dbdf4d
9 changed files with 90 additions and 72 deletions

View file

@ -33,7 +33,7 @@ public final class BenevolentRiverSpirit extends CardImpl {
this.getSpellAbility().addCost(new WaterbendCost(5)); this.getSpellAbility().addCost(new WaterbendCost(5));
this.addAbility(new SimpleStaticAbility( this.addAbility(new SimpleStaticAbility(
Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {5}") Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {5}")
)); ).setRuleAtTheTop(true));
// Flying // Flying
this.addAbility(FlyingAbility.getInstance()); this.addAbility(FlyingAbility.getInstance());

View file

@ -1,13 +1,16 @@
package mage.cards.c; package mage.cards.c;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.WaterbendXCost; import mage.abilities.costs.common.WaterbendXCost;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.TapTargetEffect; import mage.abilities.effects.common.TapTargetEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.common.FilterOpponentsCreaturePermanent; import mage.filter.common.FilterOpponentsCreaturePermanent;
@ -31,6 +34,9 @@ public final class CrashingWave extends CardImpl {
// As an additional cost to cast this spell, waterbend {X}. // As an additional cost to cast this spell, waterbend {X}.
this.getSpellAbility().addCost(new WaterbendXCost()); this.getSpellAbility().addCost(new WaterbendXCost());
this.addAbility(new SimpleStaticAbility(
Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {X}")
).setRuleAtTheTop(true));
// Tap up to X target creatures, then distribute three stun counters among tapped creatures your opponents control. // Tap up to X target creatures, then distribute three stun counters among tapped creatures your opponents control.
this.getSpellAbility().addEffect(new TapTargetEffect("tap up to X target creatures")); this.getSpellAbility().addEffect(new TapTargetEffect("tap up to X target creatures"));
@ -79,7 +85,7 @@ class CrashingWaveEffect extends OneShotEffect {
} }
TargetPermanentAmount target = new TargetPermanentAmount(3, 1, filter); TargetPermanentAmount target = new TargetPermanentAmount(3, 1, filter);
target.withNotTarget(true); target.withNotTarget(true);
player.chooseTarget(outcome, target, source, game); target.chooseTarget(outcome, player.getId(), source, game);
for (UUID targetId : target.getTargets()) { for (UUID targetId : target.getTargets()) {
Optional.ofNullable(targetId) Optional.ofNullable(targetId)
.map(game::getPermanent) .map(game::getPermanent)

View file

@ -1,11 +1,12 @@
package mage.cards.f; package mage.cards.f;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.costs.common.WaterbendCost;
import mage.abilities.costs.common.WaterbendXCost; import mage.abilities.costs.common.WaterbendXCost;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenCopyTargetEffect; import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.SacrificeTargetEffect; import mage.abilities.effects.common.SacrificeTargetEffect;
import mage.cards.*; import mage.cards.*;
import mage.constants.CardType; import mage.constants.CardType;
@ -35,6 +36,9 @@ public final class FoggySwampVisions extends CardImpl {
// As an additional cost to cast this spell, waterbend {X}. // As an additional cost to cast this spell, waterbend {X}.
this.getSpellAbility().addCost(new WaterbendXCost()); this.getSpellAbility().addCost(new WaterbendXCost());
this.addAbility(new SimpleStaticAbility(
Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {X}")
).setRuleAtTheTop(true));
// Exile X target creature cards from graveyards. For each creature card exiled this way, create a token that's a copy of it. At the beginning of your next end step, sacrifice those tokens. // Exile X target creature cards from graveyards. For each creature card exiled this way, create a token that's a copy of it. At the beginning of your next end step, sacrifice those tokens.
this.getSpellAbility().addEffect(new FoggySwampVisionsEffect()); this.getSpellAbility().addEffect(new FoggySwampVisionsEffect());

View file

@ -6,7 +6,6 @@ import mage.abilities.common.ActivateIfConditionActivatedAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.common.MyTurnCondition; import mage.abilities.condition.common.MyTurnCondition;
import mage.abilities.costs.common.WaterbendXCost; import mage.abilities.costs.common.WaterbendXCost;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.InfoEffect;
@ -20,7 +19,6 @@ import mage.constants.SubType;
import mage.constants.SuperType; import mage.constants.SuperType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.permanent.token.AllyToken; import mage.game.permanent.token.AllyToken;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -49,9 +47,8 @@ public final class KataraWaterTribesHope extends CardImpl {
Ability ability = new ActivateIfConditionActivatedAbility(new SetBasePowerToughnessAllEffect( Ability ability = new ActivateIfConditionActivatedAbility(new SetBasePowerToughnessAllEffect(
GetXValue.instance, GetXValue.instance, Duration.EndOfTurn, GetXValue.instance, GetXValue.instance, Duration.EndOfTurn,
StaticFilters.FILTER_CONTROLLED_CREATURES StaticFilters.FILTER_CONTROLLED_CREATURES
), new WaterbendXCost(), MyTurnCondition.instance); ), new WaterbendXCost(1), MyTurnCondition.instance);
ability.addEffect(new InfoEffect("X can't be 0")); ability.addEffect(new InfoEffect("X can't be 0"));
CardUtil.castStream(ability.getCosts(), VariableManaCost.class).forEach(cost -> cost.setMinX(1));
this.addAbility(ability); this.addAbility(ability);
} }

View file

@ -28,7 +28,7 @@ public final class WaterWhip extends CardImpl {
this.getSpellAbility().addCost(new WaterbendCost(5)); this.getSpellAbility().addCost(new WaterbendCost(5));
this.addAbility(new SimpleStaticAbility( this.addAbility(new SimpleStaticAbility(
Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {5}") Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {5}")
)); ).setRuleAtTheTop(true));
// Return up to two target creatures to their owners' hands. Draw two cards. // Return up to two target creatures to their owners' hands. Draw two cards.
this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); this.getSpellAbility().addEffect(new ReturnToHandTargetEffect());

View file

@ -1,11 +1,14 @@
package mage.cards.w; package mage.cards.w;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.WaterbendXCost; import mage.abilities.costs.common.WaterbendXCost;
import mage.abilities.effects.common.ExileReturnBattlefieldNextEndStepTargetEffect; import mage.abilities.effects.common.ExileReturnBattlefieldNextEndStepTargetEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.targetadjustment.XTargetsCountAdjuster; import mage.target.targetadjustment.XTargetsCountAdjuster;
@ -23,6 +26,9 @@ public final class WaterbendersRestoration extends CardImpl {
// As an additional cost to cast this spell, waterbend {X}. // As an additional cost to cast this spell, waterbend {X}.
this.getSpellAbility().addCost(new WaterbendXCost()); this.getSpellAbility().addCost(new WaterbendXCost());
this.addAbility(new SimpleStaticAbility(
Zone.ALL, new InfoEffect("as an additional cost to cast this spell, waterbend {X}")
).setRuleAtTheTop(true));
// Exile X target creatures you control. Return those cards to the battlefield under their owner's control at the beginning of the next end step. // Exile X target creatures you control. Return those cards to the battlefield under their owner's control at the beginning of the next end step.
this.getSpellAbility().addEffect(new ExileReturnBattlefieldNextEndStepTargetEffect() this.getSpellAbility().addEffect(new ExileReturnBattlefieldNextEndStepTargetEffect()

View file

@ -8,6 +8,7 @@ import mage.abilities.condition.Condition;
import mage.abilities.costs.*; import mage.abilities.costs.*;
import mage.abilities.costs.common.PayLifeCost; import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.costs.common.WaterbendCost; import mage.abilities.costs.common.WaterbendCost;
import mage.abilities.costs.common.WaterbendXCost;
import mage.abilities.costs.mana.*; import mage.abilities.costs.mana.*;
import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
@ -814,54 +815,59 @@ public abstract class AbilityImpl implements Ability {
} }
} }
} }
if (variableManaCost != null) { if (variableManaCost == null) {
if (!variableManaCost.isPaid()) { // should only happen for human players return variableManaCost;
int xValue;
if (!noMana || variableManaCost.getCostType().canUseAnnounceOnFreeCast()) {
if (variableManaCost.wasAnnounced()) {
// announce by rules
xValue = variableManaCost.getAmount();
} else {
// announce by player
xValue = controller.announceX(variableManaCost.getMinX(), variableManaCost.getMaxX(),
"Announce the value for " + variableManaCost.getText(), game, this, true);
}
int amountMana = xValue * variableManaCost.getXInstancesCount();
StringBuilder manaString = threadLocalBuilder.get();
if (variableManaCost.getFilter() == null || variableManaCost.getFilter().isGeneric()) {
manaString.append('{').append(amountMana).append('}');
} else {
String manaSymbol = null;
if (variableManaCost.getFilter().isBlack()) {
if (variableManaCost.getFilter().isRed()) {
manaSymbol = "B/R";
} else {
manaSymbol = "B";
}
} else if (variableManaCost.getFilter().isRed()) {
manaSymbol = "R";
} else if (variableManaCost.getFilter().isBlue()) {
manaSymbol = "U";
} else if (variableManaCost.getFilter().isGreen()) {
manaSymbol = "G";
} else if (variableManaCost.getFilter().isWhite()) {
manaSymbol = "W";
}
if (manaSymbol == null) {
throw new UnsupportedOperationException("ManaFilter is not supported: " + this);
}
for (int i = 0; i < amountMana; i++) {
manaString.append('{').append(manaSymbol).append('}');
}
}
addManaCostsToPay(new ManaCostsImpl<>(manaString.toString()));
getManaCostsToPay().setX(xValue, amountMana);
setCostsTag("X", xValue);
}
variableManaCost.setPaid();
}
} }
if (variableManaCost.isPaid()) {
return variableManaCost;
} // should only happen for human players
int xValue;
if (!noMana || variableManaCost.getCostType().canUseAnnounceOnFreeCast()) {
if (variableManaCost.wasAnnounced()) {
// announce by rules
xValue = variableManaCost.getAmount();
} else {
// announce by player
xValue = controller.announceX(variableManaCost.getMinX(), variableManaCost.getMaxX(),
"Announce the value for " + variableManaCost.getText(), game, this, true);
}
int amountMana = xValue * variableManaCost.getXInstancesCount();
StringBuilder manaString = threadLocalBuilder.get();
if (!(variableManaCost instanceof WaterbendXCost)) {
if (variableManaCost.getFilter() == null || variableManaCost.getFilter().isGeneric()) {
manaString.append('{').append(amountMana).append('}');
} else {
String manaSymbol;
if (variableManaCost.getFilter().isBlack()) {
if (variableManaCost.getFilter().isRed()) {
manaSymbol = "B/R";
} else {
manaSymbol = "B";
}
} else if (variableManaCost.getFilter().isRed()) {
manaSymbol = "R";
} else if (variableManaCost.getFilter().isBlue()) {
manaSymbol = "U";
} else if (variableManaCost.getFilter().isGreen()) {
manaSymbol = "G";
} else if (variableManaCost.getFilter().isWhite()) {
manaSymbol = "W";
} else {
throw new UnsupportedOperationException("ManaFilter is not supported: " + this);
}
for (int i = 0; i < amountMana; i++) {
manaString.append('{').append(manaSymbol).append('}');
}
}
addManaCostsToPay(new ManaCostsImpl<>(manaString.toString()));
} else {
addManaCostsToPay(new WaterbendCost(amountMana));
}
getManaCostsToPay().setX(xValue, amountMana);
setCostsTag("X", xValue);
}
variableManaCost.setPaid();
return variableManaCost; return variableManaCost;
} }

View file

@ -12,6 +12,9 @@ import mage.abilities.costs.mana.GenericManaCost;
* (usually because the waterbend cost itself is an additional cost), the alternate method to pay for mana * (usually because the waterbend cost itself is an additional cost), the alternate method to pay for mana
* described in rule 701.67a may be used only to pay for the amount of generic mana in the waterbend cost, * described in rule 701.67a may be used only to pay for the amount of generic mana in the waterbend cost,
* even if the total cost to cast that spell or activate that ability includes other generic mana components. * even if the total cost to cast that spell or activate that ability includes other generic mana components.
* <p>
* If you need Waterbend {X} then use {@link WaterbendXCost}
* If using as an additional cost for a spell, add an ability with an InfoEffect for proper text generation (see WaterWhip)
* *
* @author TheElk801 * @author TheElk801
*/ */

View file

@ -1,22 +1,23 @@
package mage.abilities.costs.common; package mage.abilities.costs.common;
import mage.abilities.Ability; import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.Cost; import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.costs.CostImpl;
import mage.game.Game;
import java.util.UUID;
/** /**
* TODO: Implement properly * Used for Waterbend {X} costs, otherwise use {@link WaterbendCost}
* If using as an additional cost for a spell, add an ability with an InfoEffect for proper text generation (see WaterbendersRestoration)
* *
* @author TheElk801 * @author TheElk801
*/ */
public class WaterbendXCost extends CostImpl { public class WaterbendXCost extends VariableManaCost {
public WaterbendXCost() { public WaterbendXCost() {
super(); this(0);
this.text = "waterbend {X}"; }
public WaterbendXCost(int minX) {
super(VariableCostType.NORMAL);
this.setMinX(minX);
} }
private WaterbendXCost(final WaterbendXCost cost) { private WaterbendXCost(final WaterbendXCost cost) {
@ -29,12 +30,7 @@ public class WaterbendXCost extends CostImpl {
} }
@Override @Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { public String getText() {
return false; return "waterbend {X}";
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
return false;
} }
} }