* Additional costs - added support of X costs on free cast (example: Kicker X, see Thieving Skydiver and Etali, Primal Storm combo);

* As an additional cost discard X cards - fixed wrong text (example: Channeled Force, Firestorm);
This commit is contained in:
Oleg Agafonov 2021-08-05 16:18:04 +04:00
parent d62cf17422
commit 53aababd44
65 changed files with 483 additions and 417 deletions

View file

@ -1,12 +1,12 @@
package mage.cards.a;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.common.TapTargetCost;
import mage.abilities.costs.mana.ManaCostsImpl;
@ -15,11 +15,7 @@ import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.keyword.VigilanceAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.constants.*;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.PowerPredicate;
@ -30,8 +26,9 @@ import mage.target.common.TargetControlledPermanent;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetadjustment.TargetAdjuster;
import java.util.UUID;
/**
*
* @author jack-the-BOSS
*/
public final class AryelKnightOfWindgrace extends CardImpl {
@ -83,7 +80,7 @@ class AryelTapXTargetCost extends VariableCostImpl {
}
public AryelTapXTargetCost() {
super("controlled untapped Knights you would like to tap");
super(VariableCostType.NORMAL, "controlled untapped Knights you would like to tap");
this.text = "Tap X untapped Knights you control";
}

View file

@ -1,7 +1,5 @@
package mage.cards.b;
import java.util.UUID;
import mage.ObjectColor;
import mage.abilities.costs.AlternativeCostSourceAbility;
import mage.abilities.costs.common.ExileFromHandCost;
@ -20,14 +18,15 @@ import mage.filter.predicate.mageobject.ColorPredicate;
import mage.target.common.TargetCardInHand;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class BlazingShoal extends CardImpl {
public BlazingShoal(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{X}{R}{R}");
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{X}{R}{R}");
this.subtype.add(SubType.ARCANE);
@ -35,7 +34,7 @@ public final class BlazingShoal extends CardImpl {
FilterOwnedCard filter = new FilterOwnedCard("a red card with mana value X from your hand");
filter.add(new ColorPredicate(ObjectColor.RED));
filter.add(Predicates.not(new CardIdPredicate(this.getId()))); // the exile cost can never be paid with the card itself
this.addAbility(new AlternativeCostSourceAbility(new ExileFromHandCost(new TargetCardInHand(filter),true)));
this.addAbility(new AlternativeCostSourceAbility(new ExileFromHandCost(new TargetCardInHand(filter), true)));
// Target creature gets +X/+0 until end of turn.
this.getSpellAbility().addEffect(new BoostTargetEffect(ExileFromHandCostCardConvertedMana.instance, StaticValue.get(0), Duration.EndOfTurn));

View file

@ -1,12 +1,12 @@
package mage.cards.c;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.RemoveCountersSourceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
@ -26,8 +26,9 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetAnyTarget;
import java.util.UUID;
/**
*
* @author jmharmon
*/
public final class ChamberSentry extends CardImpl {
@ -69,7 +70,7 @@ public final class ChamberSentry extends CardImpl {
class ChamberSentryRemoveVariableCountersSourceCost extends VariableCostImpl {
protected int minimalCountersToPay = 0;
private String counterName;
private final String counterName;
public ChamberSentryRemoveVariableCountersSourceCost(Counter counter) {
this(counter, 0);
@ -84,7 +85,7 @@ class ChamberSentryRemoveVariableCountersSourceCost extends VariableCostImpl {
}
public ChamberSentryRemoveVariableCountersSourceCost(Counter counter, int minimalCountersToPay, String text) {
super(counter.getName() + " counters to remove");
super(VariableCostType.NORMAL, counter.getName() + " counters to remove");
this.minimalCountersToPay = minimalCountersToPay;
this.counterName = counter.getName();
if (text == null || text.isEmpty()) {

View file

@ -25,7 +25,7 @@ public final class ChanneledForce extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{U}{R}");
// As an additional cost to cast this spell, discard X cards.
this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, false));
this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true));
// Target player draws X cards. Channeled Force deals X damage to up to one target creature or planeswalker.
this.getSpellAbility().addEffect(new ChanneledForceEffect());

View file

@ -1,7 +1,5 @@
package mage.cards.c;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
@ -18,8 +16,9 @@ import mage.constants.Zone;
import mage.counters.CounterType;
import mage.filter.common.FilterControlledLandPermanent;
import java.util.UUID;
/**
*
* @author Rene - bugisemail at gmail.com
*/
public final class CopperLeafAngel extends CardImpl {
@ -35,8 +34,8 @@ public final class CopperLeafAngel extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// {tap}, Sacrifice X lands: Put X +1/+1 counters on Copper-Leaf Angel.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance(),GetXValue.instance, false), new TapSourceCost());
ability.addCost(new SacrificeXTargetCost(new FilterControlledLandPermanent("lands"), false));
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance(), GetXValue.instance, false), new TapSourceCost());
ability.addCost(new SacrificeXTargetCost(new FilterControlledLandPermanent("lands")));
this.addAbility(ability);
}

View file

@ -18,6 +18,7 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import mage.util.ManaUtil;
import java.util.UUID;
@ -79,7 +80,7 @@ class CrystalShardEffect extends OneShotEffect {
if (player == null) {
return true;
}
Cost cost = new GenericManaCost(1);
Cost cost = ManaUtil.createManaCost(1, false);
String message = "Pay {1}? (Otherwise " + targetCreature.getName() + " will be returned to its owner's hand)";
if (player.chooseUse(Outcome.Benefit, message, source, game)) {
cost.pay(source, game, source, targetCreature.getControllerId(), false, null);

View file

@ -1,10 +1,9 @@
package mage.cards.d;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.DiscardTargetCost;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.common.DamageAllEffect;
@ -19,14 +18,15 @@ import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
import java.util.UUID;
/**
*
* @author emerald000
*/
public final class DevastatingDreams extends CardImpl {
public DevastatingDreams(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{R}{R}");
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}{R}");
// As an additional cost to cast Devastating Dreams, discard X cards at random.
this.getSpellAbility().addCost(new DevastatingDreamsAdditionalCost());
@ -51,7 +51,7 @@ public final class DevastatingDreams extends CardImpl {
class DevastatingDreamsAdditionalCost extends VariableCostImpl {
DevastatingDreamsAdditionalCost() {
super("cards to discard randomly");
super(VariableCostType.ADDITIONAL, "cards to discard randomly");
this.text = "as an additional cost to cast this spell, discard X cards at random";
}

View file

@ -5,6 +5,7 @@ import mage.abilities.Ability;
import mage.abilities.common.ActivateIfConditionActivatedAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.condition.common.MyTurnCondition;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.VariableManaCost;
@ -47,7 +48,7 @@ public final class EverythingamajigC extends CardImpl {
// Chimeric Staff
// X: Everythingamajig becomes an X/X Construct artifact creature until end of turn.
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new ChimericStaffEffect(), new VariableManaCost()));
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new ChimericStaffEffect(), new VariableManaCost(VariableCostType.NORMAL)));
}
private EverythingamajigC(final EverythingamajigC card) {

View file

@ -63,7 +63,7 @@ public final class ExtusOriqOverlord extends ModalDoubleFacesCard {
// Awaken the Blood Avatar
// Sorcery
// As an additional cost to cast this spell, you may sacrifice any number of creatures. This spell costs {2} less to cast for each creature sacrificed this way.
Cost cost = new SacrificeXTargetCost(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT);
Cost cost = new SacrificeXTargetCost(StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT, true);
cost.setText("As an additional cost to cast this spell, you may sacrifice any number of creatures. " +
"This spell costs {2} less to cast for each creature sacrificed this way");
this.getRightHalfCard().getSpellAbility().addCost(cost);

View file

@ -27,7 +27,7 @@ public final class Firestorm extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}");
// As an additional cost to cast Firestorm, discard X cards.
this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), false));
this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true));
// Firestorm deals X damage to each of X target creatures and/or players.
this.getSpellAbility().addEffect(new FirestormEffect());

View file

@ -5,6 +5,7 @@ import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.common.TapTargetCost;
import mage.abilities.effects.OneShotEffect;
@ -114,7 +115,7 @@ class GlacianPowerstoneEngineerCost extends VariableCostImpl {
}
GlacianPowerstoneEngineerCost() {
super("controlled untapped artifacts you would like to tap");
super(VariableCostType.NORMAL, "controlled untapped artifacts you would like to tap");
this.text = "Tap X untapped artifacts you control";
}

View file

@ -1,6 +1,5 @@
package mage.cards.h;
import java.util.UUID;
import mage.abilities.costs.common.ExileXFromYourGraveCost;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.common.DamageTargetEffect;
@ -10,8 +9,9 @@ import mage.constants.CardType;
import mage.filter.StaticFilters;
import mage.target.common.TargetPlayerOrPlaneswalker;
import java.util.UUID;
/**
*
* @author markedagain
*/
public final class HauntingMisery extends CardImpl {
@ -21,6 +21,7 @@ public final class HauntingMisery extends CardImpl {
// As an additional cost to cast Haunting Misery, exile X creature cards from your graveyard.
this.getSpellAbility().addCost(new ExileXFromYourGraveCost(StaticFilters.FILTER_CARD_CREATURE));
// Haunting Misery deals X damage to target player.
this.getSpellAbility().addTarget(new TargetPlayerOrPlaneswalker());
this.getSpellAbility().addEffect(new DamageTargetEffect(GetXValue.instance));

View file

@ -2,6 +2,7 @@ package mage.cards.h;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
@ -28,7 +29,7 @@ public final class HelmOfObedience extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}");
// {X}, {T}: Target opponent puts cards from the top of their library into their graveyard until a creature card or X cards are put into that graveyard this way, whichever comes first. If a creature card is put into that graveyard this way, sacrifice Helm of Obedience and put that card onto the battlefield under your control. X can't be 0.
VariableManaCost xCosts = new VariableManaCost();
VariableManaCost xCosts = new VariableManaCost(VariableCostType.NORMAL);
xCosts.setMinX(1);
Ability ability = new SimpleActivatedAbility(new HelmOfObedienceEffect(), xCosts);
ability.addCost(new TapSourceCost());

View file

@ -4,6 +4,7 @@ import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.common.DamageMultiEffect;
import mage.cards.CardImpl;
@ -33,7 +34,7 @@ public final class InfernalHarvest extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}");
// As an additional cost to cast Infernal Harvest, return X Swamps you control to their owner's hand.
this.getSpellAbility().addCost(new InfernalHarvestVariableCost());
this.getSpellAbility().addCost(new InfernalHarvestAdditionalCost());
// Infernal Harvest deals X damage divided as you choose among any number of target creatures.
this.getSpellAbility().addEffect(new DamageMultiEffect(GetXValue.instance));
@ -50,22 +51,22 @@ public final class InfernalHarvest extends CardImpl {
}
}
class InfernalHarvestVariableCost extends VariableCostImpl {
class InfernalHarvestAdditionalCost extends VariableCostImpl {
private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.SWAMP);
InfernalHarvestVariableCost() {
super("Swamps to return");
InfernalHarvestAdditionalCost() {
super(VariableCostType.ADDITIONAL, "Swamps to return");
this.text = "return " + xText + " Swamps you control to their owner's hand";
}
private InfernalHarvestVariableCost(final InfernalHarvestVariableCost cost) {
private InfernalHarvestAdditionalCost(final InfernalHarvestAdditionalCost cost) {
super(cost);
}
@Override
public InfernalHarvestVariableCost copy() {
return new InfernalHarvestVariableCost(this);
public InfernalHarvestAdditionalCost copy() {
return new InfernalHarvestAdditionalCost(this);
}
@Override

View file

@ -1,19 +1,11 @@
package mage.cards.i;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.common.DiscardTargetCost;
import mage.abilities.costs.common.DiscardXTargetCost;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.*;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
@ -21,20 +13,20 @@ import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInHand;
import mage.target.common.TargetCardInLibrary;
import java.util.UUID;
/**
*
* @author spjspj
*/
public final class InsidiousDreams extends CardImpl {
public InsidiousDreams(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{3}{B}");
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}");
// As an additional cost to cast Insidious Dreams, discard X cards.
this.getSpellAbility().addCost(new InsidiousDreamsAdditionalCost());
this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true));
// Search your library for X cards. Then shuffle your library and put those cards on top of it in any order.
this.getSpellAbility().addEffect(new InsidiousDreamsEffect());
@ -108,35 +100,3 @@ class InsidiousDreamsEffect extends OneShotEffect {
return false;
}
}
class InsidiousDreamsAdditionalCost extends VariableCostImpl {
InsidiousDreamsAdditionalCost() {
super("cards to discard");
this.text = "discard X cards";
}
InsidiousDreamsAdditionalCost(final InsidiousDreamsAdditionalCost cost) {
super(cost);
}
@Override
public InsidiousDreamsAdditionalCost copy() {
return new InsidiousDreamsAdditionalCost(this);
}
@Override
public int getMaxValue(Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
return controller.getHand().size();
}
return 0;
}
@Override
public Cost getFixedCostsFromAnnouncedValue(int xValue) {
TargetCardInHand target = new TargetCardInHand(xValue, new FilterCard());
return new DiscardTargetCost(target);
}
}

View file

@ -3,6 +3,7 @@ package mage.cards.l;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.Effect;
@ -27,7 +28,7 @@ public final class LiquidFire extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{R}{R}");
// As an additional cost to cast Liquid Fire, choose a number between 0 and 5.
this.getSpellAbility().addCost(new LiquidFireCost());
this.getSpellAbility().addCost(new LiquidFireAdditionalCost());
// Liquid Fire deals X damage to target creature and 5 minus X damage to that creature's controller, where X is the chosen number.
DynamicValue choiceValue = GetXValue.instance;
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
@ -79,20 +80,22 @@ public final class LiquidFire extends CardImpl {
return new LiquidFireEffect(this);
}
}
}
static class LiquidFireCost extends VariableCostImpl {
public LiquidFireCost() {
super("Choose a Number");
class LiquidFireAdditionalCost extends VariableCostImpl {
public LiquidFireAdditionalCost() {
super(VariableCostType.ADDITIONAL, "Choose a Number");
this.text = "as an additional cost to cast this spell, choose a number between 0 and 5";
}
public LiquidFireCost(final LiquidFireCost cost) {
public LiquidFireAdditionalCost(final LiquidFireAdditionalCost cost) {
super(cost);
}
@Override
public Cost copy() {
return new LiquidFireCost(this);
return new LiquidFireAdditionalCost(this);
}
@Override
@ -104,5 +107,4 @@ public final class LiquidFire extends CardImpl {
public int getMaxValue(Ability source, Game game) {
return 5;
}
}
}

View file

@ -7,6 +7,7 @@ import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.RevealTargetFromHandCost;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
@ -84,7 +85,7 @@ class RevealVariableBlackCardsFromHandCost extends VariableCostImpl {
}
RevealVariableBlackCardsFromHandCost() {
super("black cards to reveal");
super(VariableCostType.NORMAL, "black cards to reveal");
this.text = "Reveal " + xText + " black cards from {this}";
}

View file

@ -1,25 +1,20 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.common.TapTargetCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.*;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.common.FilterControlledArtifactPermanent;
@ -29,8 +24,9 @@ import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetControlledPermanent;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class MerchantsDockhand extends CardImpl {
@ -118,7 +114,7 @@ class TapXTargetCost extends VariableCostImpl {
}
public TapXTargetCost() {
super("controlled untapped artifacts you would like to tap");
super(VariableCostType.NORMAL, "controlled untapped artifacts you would like to tap");
this.text = "Tap X untapped artifacts you control";
}

View file

@ -4,6 +4,7 @@ import mage.MageInt;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
import mage.abilities.effects.ContinuousEffectImpl;
@ -38,7 +39,7 @@ public final class MirrorEntity extends CardImpl {
Ability ability = new SimpleActivatedAbility(new SetPowerToughnessAllEffect(
ManacostVariableValue.REGULAR, ManacostVariableValue.REGULAR,
Duration.EndOfTurn, StaticFilters.FILTER_CONTROLLED_CREATURES, true
).setText("Until end of turn, creatures you control have base power and toughness X/X"), new VariableManaCost());
).setText("Until end of turn, creatures you control have base power and toughness X/X"), new VariableManaCost(VariableCostType.NORMAL));
ability.addEffect(new MirrorEntityEffect());
this.addAbility(ability);
}

View file

@ -1,11 +1,8 @@
package mage.cards.n;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.common.DiscardTargetCost;
import mage.abilities.costs.common.DiscardXTargetCost;
import mage.abilities.dynamicvalue.common.DiscardCostCardManaValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamageTargetEffect;
@ -15,8 +12,6 @@ import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
import mage.target.common.TargetCreatureOrPlaneswalker;
import mage.target.targetadjustment.TargetAdjuster;
@ -31,7 +26,7 @@ public final class NahirisWrath extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}");
// As an additional cost to cast Nahiri's Wrath, discard X cards.
this.getSpellAbility().addCost(new NahirisWrathAdditionalCost());
this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true));
// Nahiri's Wrath deals damage equal to the total converted mana cost of the discarded cards to each of up to X target creatures and/or planeswalkers.
Effect effect = new DamageTargetEffect(DiscardCostCardManaValue.instance);
@ -58,7 +53,7 @@ enum NahirisWrathAdjuster implements TargetAdjuster {
ability.getTargets().clear();
int numTargets = 0;
for (VariableCost cost : ability.getCosts().getVariableCosts()) {
if (cost instanceof NahirisWrathAdditionalCost) {
if (cost instanceof DiscardXTargetCost) {
numTargets = cost.getAmount();
break;
}
@ -68,35 +63,3 @@ enum NahirisWrathAdjuster implements TargetAdjuster {
}
}
}
class NahirisWrathAdditionalCost extends VariableCostImpl {
NahirisWrathAdditionalCost() {
super("cards to discard");
this.text = "discard X cards";
}
NahirisWrathAdditionalCost(final NahirisWrathAdditionalCost cost) {
super(cost);
}
@Override
public NahirisWrathAdditionalCost copy() {
return new NahirisWrathAdditionalCost(this);
}
@Override
public int getMaxValue(Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
return controller.getHand().size();
}
return 0;
}
@Override
public Cost getFixedCostsFromAnnouncedValue(int xValue) {
TargetCardInHand target = new TargetCardInHand(xValue, new FilterCard("cards to discard"));
return new DiscardTargetCost(target);
}
}

View file

@ -26,7 +26,7 @@ public final class NostalgicDreams extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}{G}");
// As an additional cost to cast Nostalgic Dreams, discard X cards.
this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), false));
this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true));
// Return X target cards from your graveyard to your hand.
Effect effect = new ReturnFromGraveyardToHandTargetEffect();

View file

@ -1,7 +1,5 @@
package mage.cards.p;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
@ -12,16 +10,17 @@ import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.dynamicvalue.common.SignInversionDynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.constants.SubType;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author jeffwadsworth
*/
public final class Painbringer extends CardImpl {
@ -34,7 +33,7 @@ public final class Painbringer extends CardImpl {
this.power = new MageInt(1);
this.toughness = new MageInt(1);
// {tap}, Exile any number of cards from your graveyard: Target creature gets -X/-X until end of turn, where X is the number of cards exiled this way.
// {T}, Exile any number of cards from your graveyard: Target creature gets -X/-X until end of turn, where X is the number of cards exiled this way.
DynamicValue X = new SignInversionDynamicValue(GetXValue.instance);
Effect effect = new BoostTargetEffect(X, X, Duration.EndOfTurn);
effect.setText("Target creature gets -X/-X until end of turn, where X is the number of cards exiled this way");

View file

@ -4,6 +4,7 @@ import mage.ApprovingObject;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.effects.OneShotEffect;
@ -30,7 +31,7 @@ public final class PanopticMirror extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{5}");
// Imprint - {X}, {tap}: You may exile an instant or sorcery card with converted mana cost X from your hand.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PanopticMirrorExileEffect(), new VariableManaCost());
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PanopticMirrorExileEffect(), new VariableManaCost(VariableCostType.NORMAL));
ability.addCost(new TapSourceCost());
this.addAbility(ability.setAbilityWord(AbilityWord.IMPRINT));
// At the beginning of your upkeep, you may copy a card exiled with Panoptic Mirror. If you do, you may cast the copy without paying its mana cost.

View file

@ -1,8 +1,8 @@
package mage.cards.p;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.effects.OneShotEffect;
@ -18,6 +18,8 @@ import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import java.util.UUID;
/**
* @author Plopman
*/
@ -27,7 +29,7 @@ public final class PerniciousDeed extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}{G}");
// {X}, Sacrifice Pernicious Deed: Destroy each artifact, creature, and enchantment with converted mana cost X or less.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PerniciousDeedEffect(), new VariableManaCost());
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PerniciousDeedEffect(), new VariableManaCost(VariableCostType.NORMAL));
ability.addCost(new SacrificeSourceCost());
this.addAbility(ability);
}

View file

@ -26,7 +26,7 @@ public final class RestlessDreams extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}");
// As an additional cost to cast Restless Dreams, discard X cards.
this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, false));
this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true));
// Return X target creature cards from your graveyard to your hand.
Effect effect = new ReturnFromGraveyardToHandTargetEffect();

View file

@ -1,7 +1,5 @@
package mage.cards.r;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.RemoveVariableCountersTargetCost;
@ -19,8 +17,9 @@ import mage.counters.CounterType;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class RetributionOfTheAncients extends CardImpl {
@ -32,12 +31,11 @@ public final class RetributionOfTheAncients extends CardImpl {
}
public RetributionOfTheAncients(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{B}");
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{B}");
DynamicValue xValue = new SignInversionDynamicValue(GetXValue.instance);
// {B}, Remove X +1/+1 counters from among creatures you control: Target creature gets -X/-X until end of turn.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostTargetEffect(xValue,xValue,Duration.EndOfTurn, true), new ManaCostsImpl("{B}"));
DynamicValue xValue = new SignInversionDynamicValue(GetXValue.instance);
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostTargetEffect(xValue, xValue, Duration.EndOfTurn, true), new ManaCostsImpl("{B}"));
ability.addCost(new RemoveVariableCountersTargetCost(filter, CounterType.P1P1, "X", 0));
ability.addTarget(new TargetCreaturePermanent());
this.addAbility(ability);

View file

@ -1,10 +1,8 @@
package mage.cards.s;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
@ -15,17 +13,18 @@ import mage.constants.CardType;
import mage.constants.Zone;
import mage.target.TargetPlayer;
import java.util.UUID;
/**
*
* @author Loki
*/
public final class SandsOfDelirium extends CardImpl {
public SandsOfDelirium(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{3}");
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
// {X}, {tap}: Target player puts the top X cards of their library into their graveyard.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PutLibraryIntoGraveTargetEffect(ManacostVariableValue.REGULAR), new VariableManaCost());
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PutLibraryIntoGraveTargetEffect(ManacostVariableValue.REGULAR), new VariableManaCost(VariableCostType.NORMAL));
ability.addCost(new TapSourceCost());
ability.addTarget(new TargetPlayer());
this.addAbility(ability);

View file

@ -1,11 +1,6 @@
package mage.cards.s;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.common.DiscardTargetCost;
import mage.abilities.costs.common.DiscardXTargetCost;
import mage.abilities.dynamicvalue.common.GetXValue;
import mage.abilities.effects.common.DamageEverythingEffect;
import mage.cards.CardImpl;
@ -13,21 +8,19 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInHand;
import java.util.UUID;
/**
*
* @author fireshoes
*/
public final class SickeningDreams extends CardImpl {
public SickeningDreams(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{B}");
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}");
// As an additional cost to cast Sickening Dreams, discard X cards.
this.getSpellAbility().addCost(new SickeningDreamsAdditionalCost());
this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true));
// Sickening Dreams deals X damage to each creature and each player.
this.getSpellAbility().addEffect(new DamageEverythingEffect(GetXValue.instance, new FilterCreaturePermanent()));
@ -42,35 +35,3 @@ public final class SickeningDreams extends CardImpl {
return new SickeningDreams(this);
}
}
class SickeningDreamsAdditionalCost extends VariableCostImpl {
SickeningDreamsAdditionalCost() {
super("cards to discard");
this.text = "discard X cards";
}
SickeningDreamsAdditionalCost(final SickeningDreamsAdditionalCost cost) {
super(cost);
}
@Override
public SickeningDreamsAdditionalCost copy() {
return new SickeningDreamsAdditionalCost(this);
}
@Override
public int getMaxValue(Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
return controller.getHand().size();
}
return 0;
}
@Override
public Cost getFixedCostsFromAnnouncedValue(int xValue) {
TargetCardInHand target = new TargetCardInHand(xValue, new FilterCard());
return new DiscardTargetCost(target);
}
}

View file

@ -1,7 +1,5 @@
package mage.cards.s;
import java.util.UUID;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
@ -21,10 +19,10 @@ import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.game.permanent.token.GoatToken;
import java.util.UUID;
/**
*
* @author jeffwadsworth
*
*/
public final class SpringjackPasture extends CardImpl {
@ -35,7 +33,7 @@ public final class SpringjackPasture extends CardImpl {
}
public SpringjackPasture(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.LAND},"");
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
// {tap}: Add {C}.
this.addAbility(new ColorlessManaAbility());
@ -45,9 +43,9 @@ public final class SpringjackPasture extends CardImpl {
ability.addCost(new TapSourceCost());
this.addAbility(ability);
// {tap}, Sacrifice X Goats: Add X mana of any one color. You gain X life.
// {T}, Sacrifice X Goats: Add X mana of any one color. You gain X life.
ability = new DynamicManaAbility(
new Mana(0, 0, 0, 0,0, 0,1,0),
new Mana(0, 0, 0, 0, 0, 0, 1, 0),
GetXValue.instance,
new TapSourceCost(),
"Add X mana of any one color",

View file

@ -1,12 +1,11 @@
package mage.cards.t;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.RemoveCountersSourceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
@ -25,8 +24,9 @@ import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetAnyTarget;
import java.util.UUID;
/**
*
* @author jerekwilson
*/
public final class TalonOfPain extends CardImpl {
@ -89,19 +89,17 @@ public final class TalonOfPain extends CardImpl {
if (controller.hasOpponent(event.getTargetId(), game)) {
// a source you control other than Talon of Pain
UUID sourceControllerId = game.getControllerId(event.getSourceId());
if (sourceControllerId != null
&& sourceControllerId.equals(this.getControllerId())
&& !this.getSourceId().equals(event.getSourceId())) {
// return true so the effect will fire and a charge counter will be added
return true;
}
return sourceControllerId != null
&& sourceControllerId.equals(this.getControllerId())
&& !this.getSourceId().equals(event.getSourceId());
}
return false;
}
@Override
public String getTriggerPhrase() {
return "Whenever a source you control other than {this} deals damage to an opponent, " ;
return "Whenever a source you control other than {this} deals damage to an opponent, ";
}
}
}
@ -109,7 +107,7 @@ public final class TalonOfPain extends CardImpl {
class TalonOfPainRemoveVariableCountersSourceCost extends VariableCostImpl {
protected int minimalCountersToPay = 0;
private String counterName;
private final String counterName;
public TalonOfPainRemoveVariableCountersSourceCost(Counter counter) {
this(counter, 0);
@ -124,7 +122,7 @@ class TalonOfPainRemoveVariableCountersSourceCost extends VariableCostImpl {
}
public TalonOfPainRemoveVariableCountersSourceCost(Counter counter, int minimalCountersToPay, String text) {
super(counter.getName() + " counters to remove");
super(VariableCostType.NORMAL, counter.getName() + " counters to remove");
this.minimalCountersToPay = minimalCountersToPay;
this.counterName = counter.getName();
if (text == null || text.isEmpty()) {

View file

@ -2,6 +2,7 @@ package mage.cards.t;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
import mage.abilities.effects.ContinuousEffectImpl;
@ -27,7 +28,7 @@ public final class TestamentOfFaith extends CardImpl {
// {X}: Testament of Faith becomes an X/X Wall creature with defender in addition to its other types until end of turn.
Ability ability = new SimpleActivatedAbility(new SetPowerToughnessSourceEffect(
ManacostVariableValue.REGULAR, Duration.EndOfTurn, SubLayer.SetPT_7b
).setText("{this} becomes an X/X"), new VariableManaCost());
).setText("{this} becomes an X/X"), new VariableManaCost(VariableCostType.NORMAL));
ability.addEffect(new TestamentOfFaithEffect());
ability.addEffect(new GainAbilitySourceEffect(
DefenderAbility.getInstance(), Duration.EndOfTurn

View file

@ -38,7 +38,7 @@ public final class ThievingSkydiver extends CardImpl {
// Kicker {X}. X can't be 0.
KickerAbility kickerAbility = new KickerAbility("{X}");
kickerAbility.getKickerCosts().stream().forEach(cost -> {
kickerAbility.getKickerCosts().forEach(cost -> {
cost.setMinimumCost(1);
cost.setReminderText(". X can't be 0.");
});

View file

@ -2,6 +2,7 @@ package mage.cards.t;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
@ -21,7 +22,6 @@ import mage.target.common.TargetOpponent;
import java.util.UUID;
/**
*
* @author noahg
*/
public final class ThoughtDissector extends CardImpl {
@ -31,7 +31,7 @@ public final class ThoughtDissector extends CardImpl {
// {X}, {tap}: Target opponent reveals cards from the top of their library until an artifact card or X cards are revealed, whichever comes first. If an artifact card is revealed this way, put it onto the battlefield under your control and sacrifice Thought Dissector. Put the rest of the revealed cards into that player's graveyard.
SimpleActivatedAbility abilitiy = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ThoughtDissectorEffect(), new VariableManaCost());
SimpleActivatedAbility abilitiy = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ThoughtDissectorEffect(), new VariableManaCost(VariableCostType.NORMAL));
abilitiy.addCost(new TapSourceCost());
abilitiy.addTarget(new TargetOpponent());
this.addAbility(abilitiy);
@ -82,7 +82,7 @@ class ThoughtDissectorEffect extends OneShotEffect {
break;
} else {
numberOfCard++;
if (numberOfCard > max){
if (numberOfCard > max) {
break;
}
nonArtifacts.add(card);

View file

@ -26,7 +26,7 @@ public final class TurbulentDreams extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{U}{U}");
// As an additional cost to cast Turbulent Dreams, discard X cards.
this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, false));
this.getSpellAbility().addCost(new DiscardXTargetCost(StaticFilters.FILTER_CARD_CARDS, true));
// Return X target nonland permanents to their owners' hands.
Effect effect = new ReturnToHandTargetEffect();

View file

@ -25,7 +25,7 @@ public final class VengefulDreams extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}{W}");
// As an additional cost to cast Vengeful Dreams, discard X cards.
this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), false));
this.getSpellAbility().addCost(new DiscardXTargetCost(new FilterCard("cards"), true));
// Exile X target attacking creatures.
Effect effect = new ExileTargetEffect();

View file

@ -1,4 +1,3 @@
package mage.cards.v;
import mage.abilities.Ability;
@ -27,7 +26,7 @@ public final class ViciousBetrayal extends CardImpl {
// As an additional cost to cast Vicious Betrayal, sacrifice any number of creatures.
this.getSpellAbility().addCost(new SacrificeXTargetCost(new FilterControlledCreaturePermanent()));
this.getSpellAbility().addCost(new SacrificeXTargetCost(new FilterControlledCreaturePermanent(), true));
// Target creature gets +2/+2 until end of turn for each creature sacrificed this way.
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
this.getSpellAbility().addEffect(new BoostTargetEffect(GetXValue.instance, GetXValue.instance, Duration.EndOfTurn));

View file

@ -3,7 +3,6 @@ package mage.cards.w;
import mage.abilities.Ability;
import mage.abilities.common.SpellCastOpponentTriggeredAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.cards.*;
import mage.constants.*;
@ -15,6 +14,7 @@ import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
import mage.util.ManaUtil;
import java.util.Objects;
import java.util.UUID;
@ -85,7 +85,7 @@ class WanderingArchaicEffect extends OneShotEffect {
if (controller == null || opponent == null || spell == null) {
return false;
}
Cost cost = new GenericManaCost(2);
Cost cost = ManaUtil.createManaCost(2, false);
if (cost.canPay(source, source, opponent.getId(), game)
&& opponent.chooseUse(outcome, "Pay {2}?", source, game)
&& cost.pay(source, game, source, opponent.getId(), false)) {

View file

@ -2,7 +2,6 @@ package mage.cards.w;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -10,6 +9,8 @@ import mage.constants.CardType;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.ManaUtil;
import java.util.Objects;
import java.util.UUID;
@ -65,7 +66,7 @@ class WhirlwindDenialEffect extends OneShotEffect {
if (player == null) {
return;
}
Cost cost = new GenericManaCost(4);
Cost cost = ManaUtil.createManaCost(4, false);
if (cost.canPay(source, source, stackObject.getControllerId(), game)
&& player.chooseUse(outcome, "Pay {4} to prevent "
+ stackObject.getIdName() + " from being countered?", source, game)

View file

@ -651,4 +651,68 @@ public class KickerTest extends CardTestPlayerBase {
assertGraveyardCount(playerA, "Marsh Casualties", 1);
assertPowerToughness(playerB, "Centaur Courser", 1, 1);
}
@Test
public void test_FreeCast_Normal() {
skipInitShuffling();
// Kicker {2}
// If Ardent Soldier was kicked, it enters the battlefield with a +1/+1 counter on it.
addCard(Zone.LIBRARY, playerA, "Ardent Soldier", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); // for kicker cost
//
// Whenever Etali, Primal Storm attacks, exile the top card of each player's library,
// then you may cast any number of nonland cards exiled this way without paying their mana costs.
addCard(Zone.BATTLEFIELD, playerA, "Etali, Primal Storm", 1);
checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Ardent Soldier", false);
// attack and prepare free cast, use kicker
attack(1, playerA, "Etali, Primal Storm", playerB);
setChoice(playerA, true); // cast for free
setChoice(playerA, "Ardent Soldier"); // cast for free
setChoice(playerA, true); // use kicker
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertCounterCount(playerA, "Ardent Soldier", CounterType.P1P1, 1); // from kicker
}
@Test
public void test_FreeCast_MinXValueMustWork() {
// bug:
// Can cast Thieving Skydiver with kicker's X = 0 on Etali, Primal Storm
skipInitShuffling();
// Kicker {X}. X can't be 0.
// When Thieving Skydiver enters the battlefield, if it was kicked, gain control of target artifact with
// converted mana cost X or less. If that artifact is an Equipment, attach it to Thieving Skydiver.
addCard(Zone.LIBRARY, playerA, "Thieving Skydiver", 1); // {1}{U}
addCard(Zone.BATTLEFIELD, playerB, "Brain in a Jar", 1); // {2}
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); // for kicker cost
//
// Whenever Etali, Primal Storm attacks, exile the top card of each player's library,
// then you may cast any number of nonland cards exiled this way without paying their mana costs.
addCard(Zone.BATTLEFIELD, playerA, "Etali, Primal Storm", 1);
checkPlayableAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Thieving Skydiver", false);
// attack and prepare free cast
attack(1, playerA, "Etali, Primal Storm", playerB);
setChoice(playerA, true); // cast for free
setChoice(playerA, "Thieving Skydiver"); // cast for free
setChoice(playerA, true); // use kicker
setChoiceAmount(playerA, 2); // X=2 for Kicker X
addTarget(playerA, "Brain in a Jar"); // kicker's target (take control of artifact)
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Brain in a Jar", 1);
}
}

View file

@ -1,5 +1,6 @@
package org.mage.test.cards.continuous;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.mana.VariableManaCost;
import mage.constants.PhaseStep;
import mage.constants.Zone;
@ -182,7 +183,7 @@ public class UnboundFlourishingTest extends CardTestPlayerBase {
int xInstancesCount = 2;
int xAnnouncedValue = 3;
int xMultiplier = 2;
VariableManaCost cost = new VariableManaCost(xInstancesCount);
VariableManaCost cost = new VariableManaCost(VariableCostType.NORMAL, xInstancesCount);
cost.setAmount(xAnnouncedValue * xMultiplier, xAnnouncedValue * xInstancesCount, false);
Assert.assertEquals("instances count", xInstancesCount, cost.getXInstancesCount());

View file

@ -259,7 +259,7 @@ public abstract class AbilityImpl implements Ability {
if (!this.getManaCostsToPay().getVariableCosts().isEmpty()) {
int xValue = this.getManaCostsToPay().getX();
this.getManaCostsToPay().clear();
VariableManaCost xCosts = new VariableManaCost();
VariableManaCost xCosts = new VariableManaCost(VariableCostType.ADDITIONAL);
// no x events - rules from Unbound Flourishing:
// - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost.
xCosts.setAmount(xValue, xValue, false);
@ -555,9 +555,17 @@ public abstract class AbilityImpl implements Ability {
* @return variableManaCost for posting to log later
*/
protected VariableManaCost handleManaXCosts(Game game, boolean noMana, Player controller) {
// 20121001 - 601.2b
// If the spell has a variable cost that will be paid as it's being cast (such as an {X} in
// its mana cost; see rule 107.3), the player announces the value of that variable.
// 20210723 - 601.2b
// If the spell has alternative or additional costs that will
// be paid as its being cast such as buyback or kicker costs (see rules 118.8 and 118.9),
// the player announces their intentions to pay any or all of those costs (see rule 601.2f).
// A player cant apply two alternative methods of casting or two alternative costs to a
// single spell. If the spell has a variable cost that will be paid as its being cast
// (such as an {X} in its mana cost; see rule 107.3), the player announces the value of that
// variable. If the value of that variable is defined in the text of the spell by a choice
// that player would make later in the announcement or resolution of the spell, that player
// makes that choice at this time instead of that later time.
// TODO: Handle announcing other variable costs here like: RemoveVariableCountersSourceCost
VariableManaCost variableManaCost = null;
for (ManaCost cost : manaCostsToPay) {
@ -574,7 +582,7 @@ public abstract class AbilityImpl implements Ability {
if (!variableManaCost.isPaid()) { // should only happen for human players
int xValue;
int xValueMultiplier = handleManaXMultiplier(game, 1);
if (!noMana) {
if (!noMana || variableManaCost.getCostType().canUseAnnounceOnFreeCast()) {
xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), xValueMultiplier,
"Announce the value for " + variableManaCost.getText(), game, this);
int amountMana = xValue * variableManaCost.getXInstancesCount();

View file

@ -1,8 +1,11 @@
package mage.abilities.costs;
import mage.util.Copyable;
/**
* Virtual optional/additional cost, it must be tranformed to simple cost on resolve in your custom ability.
* Don't forget to set up cost type for variable costs
* <p>
* Example: KickerAbility.
*
* @author LevelX2
*/
public interface OptionalAdditionalCost extends Cost {
@ -77,6 +80,13 @@ public interface OptionalAdditionalCost extends Cost {
*/
int getActivateCount();
/**
* Set cost type to variable costs like additional or normal (example: Kicker)
*
* @param costType
*/
void setCostType(VariableCostType costType);
@Override
OptionalAdditionalCost copy();
}

View file

@ -166,6 +166,12 @@ public class OptionalAdditionalCostImpl extends CostsImpl<Cost> implements Optio
return activatedCounter;
}
@Override
public void setCostType(VariableCostType costType) {
this.getVariableCosts().forEach(cost -> {
cost.setCostType(costType);
});
}
@Override
public OptionalAdditionalCostImpl copy() {

View file

@ -13,6 +13,12 @@ import mage.game.Game;
*/
public interface OptionalAdditionalSourceCosts {
/**
* Warning, don't forget to set up cost type for costs, it can help with X announce
*
* @param ability
* @param game
*/
// TODO: add AI support to use buyback, replicate and other additional costs (current version can't calc available mana before buyback use)
void addOptionalAdditionalCosts(Ability ability, Game game);

View file

@ -64,4 +64,8 @@ public interface VariableCost {
* @return
*/
Cost getFixedCostsFromAnnouncedValue(int xValue);
VariableCostType getCostType();
void setCostType(VariableCostType costType);
}

View file

@ -16,6 +16,7 @@ import java.util.UUID;
public abstract class VariableCostImpl implements Cost, VariableCost {
protected UUID id;
protected VariableCostType costType;
protected String text;
protected boolean paid;
protected Targets targets;
@ -23,8 +24,8 @@ public abstract class VariableCostImpl implements Cost, VariableCost {
protected String xText;
protected String actionText;
public VariableCostImpl(String actionText) {
this("X", actionText);
public VariableCostImpl(VariableCostType costType, String actionText) {
this(costType, "X", actionText);
}
/**
@ -32,17 +33,19 @@ public abstract class VariableCostImpl implements Cost, VariableCost {
* @param actionText what happens with the value (e.g. "to tap", "to exile
* from your graveyard")
*/
public VariableCostImpl(String xText, String actionText) {
id = UUID.randomUUID();
paid = false;
targets = new Targets();
amountPaid = 0;
public VariableCostImpl(VariableCostType costType, String xText, String actionText) {
this.id = UUID.randomUUID();
this.costType = costType;
this.paid = false;
this.targets = new Targets();
this.amountPaid = 0;
this.xText = xText;
this.actionText = actionText;
}
public VariableCostImpl(final VariableCostImpl cost) {
this.id = cost.id;
this.costType = cost.costType;
this.text = cost.text;
this.paid = cost.paid;
this.targets = cost.targets.copy();
@ -107,14 +110,12 @@ public abstract class VariableCostImpl implements Cost, VariableCost {
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return true;
/* not used */
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
return true;
/* not used */
}
@Override
@ -150,4 +151,14 @@ public abstract class VariableCostImpl implements Cost, VariableCost {
}
return xValue;
}
@Override
public VariableCostType getCostType() {
return this.costType;
}
@Override
public void setCostType(VariableCostType costType) {
this.costType = costType;
}
}

View file

@ -0,0 +1,24 @@
package mage.abilities.costs;
/**
* See rules 601.2b
*
* @author JayDi85
*/
public enum VariableCostType {
NORMAL(false),
ALTERNATIVE(false),
ADDITIONAL(true);
// allows announcing X value on free cast (noMana) for additional costs, example: Kicker X
private final boolean canUseAnnounceOnFreeCast;
VariableCostType(boolean canUseAnnounceOnFreeCast) {
this.canUseAnnounceOnFreeCast = canUseAnnounceOnFreeCast;
}
public boolean canUseAnnounceOnFreeCast() {
return this.canUseAnnounceOnFreeCast;
}
}

View file

@ -3,6 +3,7 @@ package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
@ -19,9 +20,10 @@ public class DiscardXTargetCost extends VariableCostImpl {
this(filter, false);
}
public DiscardXTargetCost(FilterCard filter, boolean additionalCostText) {
super(filter.getMessage() + " to discard");
this.text = (additionalCostText ? "as an additional cost to cast this spell, discard " : "Discard ") + xText + ' ' + filter.getMessage();
public DiscardXTargetCost(FilterCard filter, boolean useAsAdditionalCost) {
super(useAsAdditionalCost ? VariableCostType.ADDITIONAL : VariableCostType.NORMAL,
filter.getMessage() + " to discard");
this.text = (useAsAdditionalCost ? "as an additional cost to cast this spell, discard " : "Discard ") + xText + ' ' + filter.getMessage();
this.filter = filter;
}

View file

@ -3,6 +3,7 @@ package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.mana.VariableManaCost;
import mage.cards.Card;
import mage.cards.Cards;
@ -23,7 +24,7 @@ import java.util.UUID;
public class ExileFromHandCost extends CostImpl {
List<Card> cards = new ArrayList<>();
private boolean setXFromCMC;
private final boolean setXFromCMC;
public ExileFromHandCost(TargetCardInHand target) {
this(target, false);
@ -32,7 +33,7 @@ public class ExileFromHandCost extends CostImpl {
/**
* @param target
* @param setXFromCMC the spells X value on the stack is set to the
* converted mana costs of the exiled card
* converted mana costs of the exiled card (alternative cost)
*/
public ExileFromHandCost(TargetCardInHand target, boolean setXFromCMC) {
this.addTarget(target);
@ -66,9 +67,10 @@ public class ExileFromHandCost extends CostImpl {
player.moveCards(cardsToExile, Zone.EXILED, ability, game);
paid = true;
if (setXFromCMC) {
VariableManaCost vmc = new VariableManaCost();
VariableManaCost vmc = new VariableManaCost(VariableCostType.ALTERNATIVE);
// no x events - rules from Unbound Flourishing:
// - Spells with additional costs that include X won't be affected by Unbound Flourishing. X must be in the spell's mana cost.
// TODO: wtf, look at setXFromCMC usage -- it used in cards with alternative costs, not additional... need to fix?
vmc.setAmount(cmc, cmc, false);
vmc.setPaid();
ability.getManaCostsToPay().add(vmc);

View file

@ -1,16 +1,15 @@
package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
/**
*
* @author LevelX2
*/
public class ExileXFromYourGraveCost extends VariableCostImpl {
@ -21,10 +20,11 @@ public class ExileXFromYourGraveCost extends VariableCostImpl {
this(filter, false);
}
public ExileXFromYourGraveCost(FilterCard filter, boolean additionalCostText) {
super(filter.getMessage() + " to exile");
public ExileXFromYourGraveCost(FilterCard filter, boolean useAsAdditionalCost) {
super(useAsAdditionalCost ? VariableCostType.ADDITIONAL : VariableCostType.NORMAL,
filter.getMessage() + " to exile");
this.filter = filter;
this.text = (additionalCostText ? "as an additional cost to cast this spell, exile " : "Exile ") + xText + ' ' + filter.getMessage();
this.text = (useAsAdditionalCost ? "as an additional cost to cast this spell, exile " : "Exile ") + xText + ' ' + filter.getMessage();
}
public ExileXFromYourGraveCost(final ExileXFromYourGraveCost cost) {

View file

@ -3,11 +3,11 @@ package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author LevelX2
*/
public class PayVariableLifeCost extends VariableCostImpl {
@ -16,9 +16,10 @@ public class PayVariableLifeCost extends VariableCostImpl {
this(false);
}
public PayVariableLifeCost(boolean additionalCostText) {
super("life to pay");
this.text = new StringBuilder(additionalCostText ? "as an additional cost to cast this spell, pay " : "Pay ")
public PayVariableLifeCost(boolean useAsAdditionalCost) {
super(useAsAdditionalCost ? VariableCostType.ADDITIONAL : VariableCostType.NORMAL,
"life to pay");
this.text = new StringBuilder(useAsAdditionalCost ? "as an additional cost to cast this spell, pay " : "Pay ")
.append(xText).append(' ').append("life").toString();
}

View file

@ -4,6 +4,7 @@ import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -24,7 +25,7 @@ public class PayVariableLoyaltyCost extends VariableCostImpl {
private int costModification = 0;
public PayVariableLoyaltyCost() {
super("loyality counters to remove");
super(VariableCostType.NORMAL, "loyality counters to remove");
this.text = "-X";
}

View file

@ -1,21 +1,20 @@
package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.counters.Counter;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author LevelX2
*/
public class RemoveVariableCountersSourceCost extends VariableCostImpl {
protected int minimalCountersToPay = 0;
private String counterName;
private final String counterName;
public RemoveVariableCountersSourceCost(Counter counter) {
this(counter, 0);
@ -30,7 +29,7 @@ public class RemoveVariableCountersSourceCost extends VariableCostImpl {
}
public RemoveVariableCountersSourceCost(Counter counter, int minimalCountersToPay, String text) {
super(counter.getName() + " counters to remove");
super(VariableCostType.NORMAL, counter.getName() + " counters to remove");
this.minimalCountersToPay = minimalCountersToPay;
this.counterName = counter.getName();
if (text == null || text.isEmpty()) {

View file

@ -1,11 +1,9 @@
package mage.abilities.costs.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.filter.FilterPermanent;
@ -13,8 +11,9 @@ import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
*
* @author LevelX
*/
public class RemoveVariableCountersTargetCost extends VariableCostImpl {
@ -32,7 +31,7 @@ public class RemoveVariableCountersTargetCost extends VariableCostImpl {
}
public RemoveVariableCountersTargetCost(FilterPermanent filter, CounterType counterTypeToRemove, String xText, int minValue) {
super(xText, new StringBuilder(counterTypeToRemove != null ? counterTypeToRemove.getName() + ' ' :"").append("counters to remove").toString());
super(VariableCostType.NORMAL, xText, new StringBuilder(counterTypeToRemove != null ? counterTypeToRemove.getName() + ' ' : "").append("counters to remove").toString());
this.filter = filter;
this.counterTypeToRemove = counterTypeToRemove;
this.text = setText();
@ -67,11 +66,11 @@ public class RemoveVariableCountersTargetCost extends VariableCostImpl {
@Override
public int getMaxValue(Ability source, Game game) {
int maxValue = 0;
for (Permanent permanent :game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) {
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, source.getControllerId(), game)) {
if (counterTypeToRemove != null) {
maxValue += permanent.getCounters(game).getCount(counterTypeToRemove);
} else {
for(Counter counter :permanent.getCounters(game).values()){
for (Counter counter : permanent.getCounters(game).values()) {
maxValue += counter.getCount();
}
}
@ -81,7 +80,7 @@ public class RemoveVariableCountersTargetCost extends VariableCostImpl {
@Override
public Cost getFixedCostsFromAnnouncedValue(int xValue) {
return new RemoveCounterCost(new TargetPermanent(minValue,Integer.MAX_VALUE, filter, true), counterTypeToRemove, xValue);
return new RemoveCounterCost(new TargetPermanent(minValue, Integer.MAX_VALUE, filter, true), counterTypeToRemove, xValue);
}
}

View file

@ -1,15 +1,14 @@
package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.target.common.TargetControlledPermanent;
/**
*
* @author LevelX2
*/
public class SacrificeXTargetCost extends VariableCostImpl {
@ -20,9 +19,10 @@ public class SacrificeXTargetCost extends VariableCostImpl {
this(filter, false);
}
public SacrificeXTargetCost(FilterControlledPermanent filter, boolean additionalCostText) {
super(filter.getMessage() + " to sacrifice");
this.text = (additionalCostText ? "as an additional cost to cast this spell, sacrifice " : "Sacrifice ") + xText + ' ' + filter.getMessage();
public SacrificeXTargetCost(FilterControlledPermanent filter, boolean useAsAdditionalCost) {
super(useAsAdditionalCost ? VariableCostType.ADDITIONAL : VariableCostType.NORMAL,
filter.getMessage() + " to sacrifice");
this.text = (useAsAdditionalCost ? "as an additional cost to cast this spell, sacrifice " : "Sacrifice ") + xText + ' ' + filter.getMessage();
this.filter = filter;
}

View file

@ -1,16 +1,14 @@
package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.VariableCostType;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
import mage.target.common.TargetControlledPermanent;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class TapVariableTargetCost extends VariableCostImpl {
@ -21,10 +19,11 @@ public class TapVariableTargetCost extends VariableCostImpl {
this(filter, false, "X");
}
public TapVariableTargetCost(FilterControlledPermanent filter, boolean additionalCostText, String xText) {
super(xText, new StringBuilder(filter.getMessage()).append(" to tap").toString());
public TapVariableTargetCost(FilterControlledPermanent filter, boolean useAsAdditionalCost, String xText) {
super(useAsAdditionalCost ? VariableCostType.ADDITIONAL : VariableCostType.NORMAL,
xText, new StringBuilder(filter.getMessage()).append(" to tap").toString());
this.filter = filter;
this.text = new StringBuilder(additionalCostText ? "as an additional cost to cast this spell, tap ":"Tap ")
this.text = new StringBuilder(useAsAdditionalCost ? "as an additional cost to cast this spell, tap " : "Tap ")
.append(this.xText).append(' ').append(filter.getMessage()).toString();
}

View file

@ -2,10 +2,7 @@ package mage.abilities.costs.mana;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.CostsImpl;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.*;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.mana.ManaOptions;
import mage.constants.ColoredManaSymbol;
@ -32,7 +29,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
protected final UUID id;
protected String text = null;
private static Map<String, ManaCosts> costsCache = new ConcurrentHashMap<>(); // must be thread safe, can't use nulls
private static final Map<String, ManaCosts> costsCache = new ConcurrentHashMap<>(); // must be thread safe, can't use nulls
public ManaCostsImpl() {
this.id = UUID.randomUUID();
@ -471,7 +468,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
modifierForX++;
}
}
this.add(new VariableManaCost(modifierForX));
this.add(new VariableManaCost(VariableCostType.NORMAL, modifierForX));
} //TODO: handle multiple {X} and/or {Y} symbols
} else if (Character.isDigit(symbol.charAt(0))) {
MonoHybridManaCost cost;

View file

@ -4,6 +4,7 @@ import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.VariableCostType;
import mage.constants.ColoredManaSymbol;
import mage.filter.FilterMana;
import mage.game.Game;
@ -18,6 +19,7 @@ public final class VariableManaCost extends ManaCostImpl implements VariableCost
// 1. as X value in spell/ability cast (announce X, set VariableManaCost as paid and add generic mana to pay instead)
// 2. as X value in direct pay (X already announced, cost is unpaid, need direct pay)
protected VariableCostType costType;
protected int xInstancesCount; // number of {X} instances in cost like {X} or {X}{X}
protected int xValue = 0; // final X value after announce and replace events
protected int xPay = 0; // final/total need pay after announce and replace events (example: {X}{X}, X=3, xPay = 6)
@ -27,11 +29,12 @@ public final class VariableManaCost extends ManaCostImpl implements VariableCost
protected int minX = 0;
protected int maxX = Integer.MAX_VALUE;
public VariableManaCost() {
this(1);
public VariableManaCost(VariableCostType costType) {
this(costType, 1);
}
public VariableManaCost(int xInstancesCount) {
public VariableManaCost(VariableCostType costType, int xInstancesCount) {
this.costType = costType;
this.xInstancesCount = xInstancesCount;
this.cost = new Mana();
options.add(new Mana());
@ -39,6 +42,7 @@ public final class VariableManaCost extends ManaCostImpl implements VariableCost
public VariableManaCost(final VariableManaCost manaCost) {
super(manaCost);
this.costType = manaCost.costType;
this.xInstancesCount = manaCost.xInstancesCount;
this.xValue = manaCost.xValue;
this.xPay = manaCost.xPay;
@ -171,4 +175,14 @@ public final class VariableManaCost extends ManaCostImpl implements VariableCost
public void setFilter(FilterMana filter) {
this.filter = filter;
}
@Override
public VariableCostType getCostType() {
return this.costType;
}
@Override
public void setCostType(VariableCostType costType) {
this.costType = costType;
}
}

View file

@ -36,25 +36,31 @@ public class BuybackAbility extends StaticAbility implements OptionalAdditionalS
private static final String keywordText = "Buyback";
private static final String reminderTextCost = "You may {cost} in addition to any other costs as you cast this spell. If you do, put this card into your hand as it resolves.";
private static final String reminderTextMana = "You may pay an additional {cost} as you cast this spell. If you do, put this card into your hand as it resolves.";
protected OptionalAdditionalCost buybackCost;
private int amountToReduceBy = 0;
public BuybackAbility(String manaString) {
super(Zone.STACK, new BuybackEffect());
this.buybackCost = new OptionalAdditionalCostImpl(keywordText, reminderTextMana, new ManaCostsImpl(manaString));
addBuybackCostAndSetup(new OptionalAdditionalCostImpl(keywordText, reminderTextMana, new ManaCostsImpl(manaString)));
setRuleAtTheTop(true);
}
public BuybackAbility(Cost cost) {
super(Zone.STACK, new BuybackEffect());
this.buybackCost = new OptionalAdditionalCostImpl(keywordText, "&mdash;", reminderTextCost, cost);
addBuybackCostAndSetup(new OptionalAdditionalCostImpl(keywordText, "&mdash;", reminderTextCost, cost));
setRuleAtTheTop(true);
}
private void addBuybackCostAndSetup(OptionalAdditionalCost newCost) {
this.buybackCost = newCost;
this.buybackCost.setCostType(VariableCostType.ADDITIONAL);
}
public BuybackAbility(final BuybackAbility ability) {
super(ability);
buybackCost = new OptionalAdditionalCostImpl((OptionalAdditionalCostImpl) ability.buybackCost);
amountToReduceBy = ability.amountToReduceBy;
this.buybackCost = ability.buybackCost.copy();
this.amountToReduceBy = ability.amountToReduceBy;
}
@Override

View file

@ -4,11 +4,9 @@ import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.*;
import mage.abilities.costs.common.TapTargetCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
@ -62,9 +60,9 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional
MORE
}
private UUID conspireId;
private final UUID conspireId;
private String reminderText;
private OptionalAdditionalCostImpl conspireCost;
private OptionalAdditionalCost conspireCost;
/**
* Unique Id for a ConspireAbility but may not change while a continuous
@ -87,12 +85,19 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional
reminderText = "As you cast this 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.";
break;
}
Cost cost = new TapTargetCost(new TargetControlledPermanent(2, 2, filter, true));
cost.setText("");
conspireCost = new OptionalAdditionalCostImpl(keywordText, " ", reminderText, cost);
addConspireCostAndSetup(new OptionalAdditionalCostImpl(keywordText, " ", reminderText, cost));
addSubAbility(new ConspireTriggeredAbility(conspireId));
}
private void addConspireCostAndSetup(OptionalAdditionalCost newCost) {
this.conspireCost = newCost;
this.conspireCost.setCostType(VariableCostType.ADDITIONAL);
}
public ConspireAbility(final ConspireAbility ability) {
super(ability);
this.conspireId = ability.conspireId;
@ -139,14 +144,18 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional
if (conspireCost.canPay(ability, this, getControllerId(), game)
&& player.chooseUse(Outcome.Benefit, "Pay " + conspireCost.getText(false) + " ?", ability, game)) {
activateConspire(ability, game);
for (Iterator it = conspireCost.iterator(); it.hasNext(); ) {
for (Iterator it = ((Costs) conspireCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
ability.getCosts().add(cost.copy());
}
}
}
}
}
}
private void activateConspire(Ability ability, Game game) {
Set<UUID> activations = (Set<UUID>) game.getState().getValue(CONSPIRE_ACTIVATION_KEY + ability.getId());
@ -194,7 +203,7 @@ public class ConspireAbility extends StaticAbility implements OptionalAdditional
class ConspireTriggeredAbility extends TriggeredAbilityImpl {
private UUID conspireId;
private final UUID conspireId;
public ConspireTriggeredAbility(UUID conspireId) {
super(Zone.STACK, new ConspireEffect());

View file

@ -3,10 +3,7 @@ package mage.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalModeSourceCosts;
import mage.abilities.costs.*;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.constants.Outcome;
import mage.constants.Zone;
@ -14,6 +11,7 @@ import mage.game.Game;
import mage.players.Player;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
@ -35,12 +33,12 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
private static final String keywordText = "Entwine";
protected static final String reminderText = "You may {cost} in addition to any other costs to use all modes.";
protected OptionalAdditionalCost additionalCost;
protected OptionalAdditionalCost entwineCost;
protected Set<String> activations = new HashSet<>(); // same logic as KickerAbility: activations per zoneChangeCounter
public EntwineAbility(String manaString) {
super(Zone.STACK, null);
this.additionalCost = new OptionalAdditionalCostImpl(keywordText, reminderText, new ManaCostsImpl(manaString));
addEntwineCostAndSetup(new OptionalAdditionalCostImpl(keywordText, reminderText, new ManaCostsImpl(manaString)));
}
public EntwineAbility(Cost cost) {
@ -49,14 +47,20 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
public EntwineAbility(Cost cost, String reminderText) {
super(Zone.STACK, null);
this.additionalCost = new OptionalAdditionalCostImpl(keywordText, "&mdash;", reminderText, cost);
addEntwineCostAndSetup(new OptionalAdditionalCostImpl(keywordText, "&mdash;", reminderText, cost));
setRuleAtTheTop(true);
}
private void addEntwineCostAndSetup(OptionalAdditionalCost newCost) {
this.entwineCost = newCost;
this.entwineCost.setCostType(VariableCostType.ADDITIONAL);
}
public EntwineAbility(final EntwineAbility ability) {
super(ability);
if (ability.additionalCost != null) {
this.additionalCost = ability.additionalCost.copy();
if (ability.entwineCost != null) {
this.entwineCost = ability.entwineCost.copy();
}
this.activations.addAll(ability.activations);
}
@ -77,32 +81,40 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
return;
}
this.resetCosts(game, ability);
if (additionalCost == null) {
this.resetEntwine(game, ability);
if (entwineCost == null) {
return;
}
if (additionalCost.canPay(ability, this, ability.getControllerId(), game)
&& player.chooseUse(Outcome.Benefit, "Pay " + additionalCost.getText(false) + " ?", ability, game)) {
addCostsToAbility(additionalCost, ability);
activateCost(game, ability);
// AI can use it
if (entwineCost.canPay(ability, this, ability.getControllerId(), game)
&& player.chooseUse(Outcome.Benefit, "Pay " + entwineCost.getText(false) + " ?", ability, game)) {
for (Iterator it = ((Costs) entwineCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
ability.getCosts().add(cost.copy());
}
}
activateEntwine(game, ability);
}
}
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();
if (additionalCost != null) {
sb.append(additionalCost.getText(false));
sb.append(' ').append(additionalCost.getReminderText());
if (entwineCost != null) {
sb.append(entwineCost.getText(false));
sb.append(' ').append(entwineCost.getReminderText());
}
return sb.toString();
}
@Override
public String getCastMessageSuffix() {
if (additionalCost != null) {
return additionalCost.getCastSuffixMessage(0);
if (entwineCost != null) {
return entwineCost.getCastSuffixMessage(0);
} else {
return "";
}
@ -119,20 +131,16 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
ability.getModes().setMaxModes(maxModes);
}
private void addCostsToAbility(Cost cost, Ability ability) {
ability.addCost(cost.copy());
}
private void resetCosts(Game game, Ability source) {
if (additionalCost != null) {
additionalCost.reset();
private void resetEntwine(Game game, Ability source) {
if (entwineCost != null) {
entwineCost.reset();
}
String key = getActivationKey(source, game);
this.activations.remove(key);
}
private void activateCost(Game game, Ability source) {
private void activateEntwine(Game game, Ability source) {
String key = getActivationKey(source, game);
this.activations.add(key);
}

View file

@ -95,17 +95,24 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
}
public final OptionalAdditionalCost addKickerCost(String manaString) {
OptionalAdditionalCost kickerCost = new OptionalAdditionalCostImpl(
OptionalAdditionalCost newCost = new OptionalAdditionalCostImpl(
keywordText, reminderText, new ManaCostsImpl(manaString));
kickerCosts.add(kickerCost);
return kickerCost;
addKickerCostAndSetup(newCost);
return newCost;
}
public final OptionalAdditionalCost addKickerCost(Cost cost) {
OptionalAdditionalCost kickerCost = new OptionalAdditionalCostImpl(
OptionalAdditionalCost newCost = new OptionalAdditionalCostImpl(
keywordText, "-", reminderText, cost);
kickerCosts.add(kickerCost);
return kickerCost;
addKickerCostAndSetup(newCost);
return newCost;
}
private void addKickerCostAndSetup(OptionalAdditionalCost newCost) {
this.kickerCosts.add(newCost);
this.kickerCosts.forEach(cost -> {
cost.setCostType(VariableCostType.ADDITIONAL);
});
}
public void resetKicker(Game game, Ability source) {
@ -250,6 +257,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
"Pay " + times + kickerCost.getText(false) + " ?", ability, game)) {
this.activateKicker(kickerCost, ability, game);
if (kickerCost instanceof Costs) {
// as multiple costs
for (Iterator itKickerCost = ((Costs) kickerCost).iterator(); itKickerCost.hasNext(); ) {
Object kickerCostObject = itKickerCost.next();
if ((kickerCostObject instanceof Costs)) {
@ -262,6 +270,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
}
}
} else {
// as single cost
addKickerCostsToAbility(kickerCost, ability, game);
}
again = kickerCost.isRepeatable();
@ -275,7 +284,8 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
}
private void addKickerCostsToAbility(Cost cost, Ability ability, Game game) {
// can contains multiple costs from multikicker ability
// can contain multiple costs from multikicker ability
// must be additional cost type
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {

View file

@ -1,11 +1,13 @@
package mage.abilities.keyword;
import mage.ApprovingObject;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.SpecialAction;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.condition.common.SuspendedCondition;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.costs.mana.VariableManaCost;
@ -26,7 +28,6 @@ import mage.target.targetpointer.FixedTarget;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.ApprovingObject;
/**
* 502.59. Suspend
@ -108,7 +109,7 @@ import mage.ApprovingObject;
*/
public class SuspendAbility extends SpecialAction {
private String ruleText;
private final String ruleText;
private boolean gainedTemporary;
/**
@ -129,7 +130,7 @@ public class SuspendAbility extends SpecialAction {
this.addEffect(new SuspendExileEffect(suspend));
this.usesStack = false;
if (suspend == Integer.MAX_VALUE) {
VariableManaCost xCosts = new VariableManaCost();
VariableManaCost xCosts = new VariableManaCost(VariableCostType.ALTERNATIVE);
xCosts.setMinX(1);
this.addManaCost(xCosts);
cost = new ManaCostsImpl("{X}" + cost.getText());
@ -191,7 +192,7 @@ public class SuspendAbility extends SpecialAction {
UUID exileId = (UUID) game.getState().getValue("SuspendExileId" + controllerId.toString());
if (exileId == null) {
exileId = UUID.randomUUID();
game.getState().setValue("SuspendExileId" + controllerId.toString(), exileId);
game.getState().setValue("SuspendExileId" + controllerId, exileId);
}
return exileId;
}
@ -306,7 +307,7 @@ class SuspendPlayCardAbility extends TriggeredAbilityImpl {
@Override
public String getRule() {
return "When the last time counter is removed from this card ({this}), "
+ "if it's removed from the game, " ;
+ "if it's removed from the game, ";
}
@Override

View file

@ -2,6 +2,7 @@ package mage.game.command.emblems;
import mage.abilities.Ability;
import mage.abilities.common.LimitedTimesPerTurnActivatedAbility;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.DiscardCardCost;
import mage.abilities.costs.mana.VariableManaCost;
import mage.abilities.effects.OneShotEffect;
@ -18,12 +19,12 @@ import mage.constants.Zone;
import mage.game.Game;
import mage.game.command.Emblem;
import mage.game.permanent.token.EmptyToken;
import mage.game.permanent.token.Token;
import mage.game.permanent.token.custom.CreatureToken;
import mage.util.CardUtil;
import mage.util.RandomUtil;
import java.util.List;
import mage.game.permanent.token.Token;
import mage.game.permanent.token.custom.CreatureToken;
/**
* @author spjspj
@ -37,7 +38,7 @@ public final class MomirEmblem extends Emblem {
// {X}, Discard a card: Create a token that's a copy of a creature card with converted mana cost X chosen at random.
// Activate this ability only any time you could cast a sorcery and only once each turn.
LimitedTimesPerTurnActivatedAbility ability = new LimitedTimesPerTurnActivatedAbility(Zone.COMMAND, new MomirEffect(), new VariableManaCost());
LimitedTimesPerTurnActivatedAbility ability = new LimitedTimesPerTurnActivatedAbility(Zone.COMMAND, new MomirEffect(), new VariableManaCost(VariableCostType.NORMAL));
ability.addCost(new DiscardCardCost());
ability.setTiming(TimingRule.SORCERY);
this.getAbilities().add(ability);
@ -65,7 +66,7 @@ class MomirEffect extends OneShotEffect {
int value = source.getManaCostsToPay().getX();
if (game.isSimulation()) {
// Create dummy token to prevent multiple DB find cards what causes H2 java.lang.IllegalStateException if AI cancels calculation because of time out
Token token = new CreatureToken(value, value +1);
Token token = new CreatureToken(value, value + 1);
token.putOntoBattlefield(1, game, source, source.getControllerId(), false, false);
return true;
}

View file

@ -6,6 +6,7 @@ import mage.ManaSymbol;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostType;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.*;
import mage.abilities.dynamicvalue.DynamicValue;
@ -648,7 +649,7 @@ public final class ManaUtil {
*/
public static ManaCost createManaCost(int genericManaCount, boolean payAsX) {
if (payAsX) {
VariableManaCost xCost = new VariableManaCost();
VariableManaCost xCost = new VariableManaCost(VariableCostType.NORMAL);
xCost.setAmount(genericManaCount, genericManaCount, false);
return xCost;
} else {