[LTC] Implement Summons of Saruman (#10720)

* [LTC] Implement Summons of Saruman

* fix constructor for Assault on Osgiliath

* fix verify failure

* refactoring a couple more Amass X
This commit is contained in:
Susucre 2023-08-01 15:51:06 +02:00 committed by GitHub
parent 1c5829f16b
commit 241226cd83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 160 additions and 101 deletions

View file

@ -1,7 +1,6 @@
package mage.cards.a;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.effects.keyword.AmassEffect;
import mage.abilities.keyword.DoubleStrikeAbility;
@ -10,11 +9,9 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import java.util.UUID;
@ -36,7 +33,7 @@ public final class AssaultOnOsgiliath extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{R}{R}{R}");
// Amass Orcs X, then Goblins and Orcs you control gain double strike and haste until end of turn.
this.getSpellAbility().addEffect(new AssaultOnOsgiliathEffect());
this.getSpellAbility().addEffect(new AmassEffect(ManacostVariableValue.REGULAR, SubType.ORC));
this.getSpellAbility().addEffect(new GainAbilityControlledEffect(
DoubleStrikeAbility.getInstance(), Duration.EndOfTurn, filter
).setText(", then Goblins and Orcs you control gain double strike"));
@ -53,26 +50,4 @@ public final class AssaultOnOsgiliath extends CardImpl {
public AssaultOnOsgiliath copy() {
return new AssaultOnOsgiliath(this);
}
}
class AssaultOnOsgiliathEffect extends OneShotEffect {
AssaultOnOsgiliathEffect() {
super(Outcome.Benefit);
staticText = "amass Orcs X";
}
private AssaultOnOsgiliathEffect(final AssaultOnOsgiliathEffect effect) {
super(effect);
}
@Override
public AssaultOnOsgiliathEffect copy() {
return new AssaultOnOsgiliathEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return AmassEffect.doAmass(source.getManaCostsToPay().getX(), SubType.ORC, game, source) != null;
}
}
}

View file

@ -9,7 +9,7 @@ import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
import mage.abilities.effects.common.TapSourceEffect;
import mage.abilities.effects.keyword.AmassEffect;
import mage.abilities.hint.ConditionHint;
@ -20,7 +20,6 @@ import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.game.Game;
import java.util.UUID;
@ -55,7 +54,10 @@ public final class BaradDur extends CardImpl {
// {X}{X}{B}, {T}: Amass Orcs X. Activate only if a creature died this turn.
Ability ability = new ActivateIfConditionActivatedAbility(
Zone.BATTLEFIELD, new BaradDurEffect(), new ManaCostsImpl<>("{X}{X}{B}"), MorbidCondition.instance
Zone.BATTLEFIELD,
new AmassEffect(ManacostVariableValue.REGULAR, SubType.ORC, false),
new ManaCostsImpl<>("{X}{X}{B}"),
MorbidCondition.instance
);
ability.addCost(new TapSourceCost());
this.addAbility(ability);
@ -69,26 +71,4 @@ public final class BaradDur extends CardImpl {
public BaradDur copy() {
return new BaradDur(this);
}
}
class BaradDurEffect extends OneShotEffect {
BaradDurEffect() {
super(Outcome.Benefit);
staticText = "amass Orcs X";
}
private BaradDurEffect(final BaradDurEffect effect) {
super(effect);
}
@Override
public BaradDurEffect copy() {
return new BaradDurEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return AmassEffect.doAmass(source.getManaCostsToPay().getX(), SubType.ORC, game, source) != null;
}
}
}

View file

@ -1,19 +1,14 @@
package mage.cards.i;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.keyword.AmassEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
@ -31,7 +26,14 @@ public final class InvadeTheCity extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{U}{R}");
// Amass X, where X is the number of instant and sorcery cards in your graveyard.
this.getSpellAbility().addEffect(new InvadeTheCityEffect());
this.getSpellAbility().addEffect(
new AmassEffect(
new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY),
SubType.ZOMBIE
).setText("amass Zombies X, where X is the number of instant and sorcery cards in your graveyard. "
+ "<i>(Put X +1/+1 counters on an Army you control. It's also a Zombie. If you don't control an Army, "
+ "create a 0/0 black Zombie Army creature token first.)</i>")
);
}
private InvadeTheCity(final InvadeTheCity card) {
@ -42,34 +44,4 @@ public final class InvadeTheCity extends CardImpl {
public InvadeTheCity copy() {
return new InvadeTheCity(this);
}
}
class InvadeTheCityEffect extends OneShotEffect {
InvadeTheCityEffect() {
super(Outcome.Benefit);
staticText = "amass Zombies X, where X is the number of instant and sorcery cards in your graveyard. " +
"<i>(Put X +1/+1 counterson an Army you control. It's also a Zombie. If you don't control an Army, " +
"create a 0/0 black Zombie Army creature token first.)</i>";
}
private InvadeTheCityEffect(final InvadeTheCityEffect effect) {
super(effect);
}
@Override
public InvadeTheCityEffect copy() {
return new InvadeTheCityEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
return AmassEffect.doAmass(player.getGraveyard().count(
StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, game
), SubType.ZOMBIE, game, source) != null;
}
}
}

View file

@ -0,0 +1,118 @@
package mage.cards.s;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.common.ExileXFromYourGraveCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.keyword.AmassEffect;
import mage.abilities.keyword.FlashbackAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.common.FilterInstantOrSorceryCard;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author Susucr
*/
public final class SummonsOfSaruman extends CardImpl {
public SummonsOfSaruman(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{U}{R}");
// Amass Orcs X. Mill X cards. You may cast an instant or sorcery spell with mana value X or less from among them without paying its mana cost. (To amass Orcs X, put X +1/+1 counters on an Army you control. Its also an Orc. If you dont control an Army, create a 0/0 black Orc Army creature token first.)
this.getSpellAbility().addEffect(new AmassEffect(SummonsOfSarumanVariableValue.instance, SubType.ORC, false));
this.getSpellAbility().addEffect(new SummonsOfSarumanEffect());
// Flashback--{3}{U}{R}, Exile X cards from your graveyard.
Ability flashback = new FlashbackAbility(this, new ManaCostsImpl<>("{3}{U}{R}"));
flashback.addCost(new ExileXFromYourGraveCost(StaticFilters.FILTER_CARD_CARDS));
this.addAbility(flashback);
}
private SummonsOfSaruman(final SummonsOfSaruman card) {
super(card);
}
@Override
public SummonsOfSaruman copy() {
return new SummonsOfSaruman(this);
}
}
enum SummonsOfSarumanVariableValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
int xValue = sourceAbility.getManaCostsToPay().getX();
for (Cost cost : sourceAbility.getCosts()) {
if (cost instanceof ExileXFromYourGraveCost) {
xValue = ((ExileXFromYourGraveCost) cost).getAmount();
}
}
return xValue;
}
@Override
public SummonsOfSarumanVariableValue copy() {
return this;
}
@Override
public String toString() {
return "X";
}
@Override
public String getMessage() {
return "";
}
}
class SummonsOfSarumanEffect extends OneShotEffect {
SummonsOfSarumanEffect() {
super(Outcome.Benefit);
staticText = "Mill X cards. You may cast an instant or sorcery spell with mana value X "
+ "or less from among them without paying its mana cost."
+ " <i>(To amass Orcs X, put X +1/+1 counters on an Army you control. It's also an Orc. "
+ "If you don't control an Army, create a 0/0 black Orc Army creature token first.)</i>";
}
private SummonsOfSarumanEffect(final SummonsOfSarumanEffect effect) {
super(effect);
}
@Override
public SummonsOfSarumanEffect copy() {
return new SummonsOfSarumanEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
int xValue = SummonsOfSarumanVariableValue.instance.calculate(game, source, this);
if (player == null || xValue < 1) {
return false;
}
Cards cards = player.millCards(xValue, source, game);
cards.retainZone(Zone.GRAVEYARD, game);
FilterCard filter = new FilterInstantOrSorceryCard();
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, xValue + 1));
CardUtil.castSpellWithAttributesForFree(player, source, game, cards, filter);
return true;
}
}

View file

@ -250,6 +250,7 @@ public final class TalesOfMiddleEarthCommander extends ExpansionSet {
cards.add(new SetCardInfo("Subjugate the Hobbits", 24, Rarity.RARE, mage.cards.s.SubjugateTheHobbits.class));
cards.add(new SetCardInfo("Sulfur Falls", 333, Rarity.RARE, mage.cards.s.SulfurFalls.class));
cards.add(new SetCardInfo("Sulfurous Springs", 334, Rarity.RARE, mage.cards.s.SulfurousSprings.class));
cards.add(new SetCardInfo("Summons of Saruman", 70, Rarity.RARE, mage.cards.s.SummonsOfSaruman.class));
cards.add(new SetCardInfo("Sunken Hollow", 335, Rarity.RARE, mage.cards.s.SunkenHollow.class));
cards.add(new SetCardInfo("Sunpetal Grove", 336, Rarity.RARE, mage.cards.s.SunpetalGrove.class));
cards.add(new SetCardInfo("Sunset Revelry", 177, Rarity.UNCOMMON, mage.cards.s.SunsetRevelry.class));

View file

@ -1,7 +1,6 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.OneShotEffect;
@ -28,7 +27,7 @@ public class MillCardsControllerEffect extends OneShotEffect {
setText();
}
public MillCardsControllerEffect(final MillCardsControllerEffect effect) {
private MillCardsControllerEffect(final MillCardsControllerEffect effect) {
super(effect);
this.numberCards = effect.numberCards;
}

View file

@ -1,6 +1,8 @@
package mage.abilities.effects.keyword;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.AddCardSubTypeTargetEffect;
import mage.constants.Duration;
@ -33,22 +35,34 @@ public class AmassEffect extends OneShotEffect {
filter.add(SubType.ARMY.getPredicate());
}
private final int amount;
private final DynamicValue amount;
private final SubType subType;
public AmassEffect(int amount, SubType subType) {
this(StaticValue.get(amount), subType);
}
public AmassEffect(DynamicValue amount, SubType subType) {
this(amount, subType, true);
}
public AmassEffect(DynamicValue amount, SubType subType, boolean withReminder) {
super(Outcome.BoostCreature);
this.amount = amount;
this.amount = amount.copy();
this.subType = subType;
staticText = "amass " + subType + "s " + amount + ". <i>(Put " + CardUtil.numberToText(amount) +
" +1/+1 counter" + (amount > 1 ? "s " : " ") + "on an Army you control. It's also " +
subType.getIndefiniteArticle() + ' ' + subType + ". If you don't control an Army, " +
"create a 0/0 black " + subType + " Army creature token first.)</i>";
staticText = "amass " + subType + "s " + amount + ".";
if (withReminder) {
staticText += " <i>(Put " + CardUtil.numberToText(amount.toString(), "a")
+ " +1/+1 counter" + (amount.toString().equals("1") ? " " : "s ") + "on an Army you control. It's also "
+ subType.getIndefiniteArticle() + ' ' + subType + ". If you don't control an Army, "
+ "create a 0/0 black " + subType + " Army creature token first.)</i>";
}
}
private AmassEffect(final AmassEffect effect) {
super(effect);
this.amount = effect.amount;
this.amount = effect.amount.copy();
this.subType = effect.subType;
}
@ -59,7 +73,7 @@ public class AmassEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
return doAmass(amount, subType, game, source) != null;
return doAmass(amount.calculate(game, source, this), subType, game, source) != null;
}
private static Token makeToken(SubType subType) {