From 241226cd8364aac02027757b3137c64134c16123 Mon Sep 17 00:00:00 2001
From: Susucre <34709007+Susucre@users.noreply.github.com>
Date: Tue, 1 Aug 2023 15:51:06 +0200
Subject: [PATCH] [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
---
.../src/mage/cards/a/AssaultOnOsgiliath.java | 31 +----
Mage.Sets/src/mage/cards/b/BaradDur.java | 32 +----
Mage.Sets/src/mage/cards/i/InvadeTheCity.java | 46 ++-----
.../src/mage/cards/s/SummonsOfSaruman.java | 118 ++++++++++++++++++
.../sets/TalesOfMiddleEarthCommander.java | 1 +
.../common/MillCardsControllerEffect.java | 3 +-
.../effects/keyword/AmassEffect.java | 30 +++--
7 files changed, 160 insertions(+), 101 deletions(-)
create mode 100644 Mage.Sets/src/mage/cards/s/SummonsOfSaruman.java
diff --git a/Mage.Sets/src/mage/cards/a/AssaultOnOsgiliath.java b/Mage.Sets/src/mage/cards/a/AssaultOnOsgiliath.java
index f7d7537f195..b38b06d67cd 100644
--- a/Mage.Sets/src/mage/cards/a/AssaultOnOsgiliath.java
+++ b/Mage.Sets/src/mage/cards/a/AssaultOnOsgiliath.java
@@ -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;
- }
-}
+}
\ No newline at end of file
diff --git a/Mage.Sets/src/mage/cards/b/BaradDur.java b/Mage.Sets/src/mage/cards/b/BaradDur.java
index 3cd97aec633..da8bcd4d1f0 100644
--- a/Mage.Sets/src/mage/cards/b/BaradDur.java
+++ b/Mage.Sets/src/mage/cards/b/BaradDur.java
@@ -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;
- }
-}
+}
\ No newline at end of file
diff --git a/Mage.Sets/src/mage/cards/i/InvadeTheCity.java b/Mage.Sets/src/mage/cards/i/InvadeTheCity.java
index 12d531af5fc..513645c2c25 100644
--- a/Mage.Sets/src/mage/cards/i/InvadeTheCity.java
+++ b/Mage.Sets/src/mage/cards/i/InvadeTheCity.java
@@ -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. "
+ + "(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.)")
+ );
}
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. " +
- "(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.)";
- }
-
- 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;
- }
-}
+}
\ No newline at end of file
diff --git a/Mage.Sets/src/mage/cards/s/SummonsOfSaruman.java b/Mage.Sets/src/mage/cards/s/SummonsOfSaruman.java
new file mode 100644
index 00000000000..bef6dd512f2
--- /dev/null
+++ b/Mage.Sets/src/mage/cards/s/SummonsOfSaruman.java
@@ -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. It’s also an Orc. If you don’t 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."
+ + " (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.)";
+ }
+
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java b/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java
index 43965d2f779..ce1131a703d 100644
--- a/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java
+++ b/Mage.Sets/src/mage/sets/TalesOfMiddleEarthCommander.java
@@ -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));
diff --git a/Mage/src/main/java/mage/abilities/effects/common/MillCardsControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MillCardsControllerEffect.java
index 58de3c44e54..36eb567759c 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/MillCardsControllerEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/MillCardsControllerEffect.java
@@ -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;
}
diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java
index e2e441f8bc9..325d042c4ad 100644
--- a/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/keyword/AmassEffect.java
@@ -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 + ". (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.)";
+
+ staticText = "amass " + subType + "s " + amount + ".";
+ if (withReminder) {
+ staticText += " (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.)";
+ }
}
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) {