diff --git a/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java b/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java new file mode 100644 index 00000000000..f0d8ec21e97 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RadiantScrollwielder.java @@ -0,0 +1,193 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.GainAbilitySpellsEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterObject; +import mage.filter.StaticFilters; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.watchers.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RadiantScrollwielder extends CardImpl { + + private static final FilterObject filter = new FilterObject("instant and sorcery spells you control"); + + static { + filter.add(Predicates.or( + CardType.INSTANT.getPredicate(), + CardType.SORCERY.getPredicate() + )); + } + + public RadiantScrollwielder(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{W}"); + + this.subtype.add(SubType.DWARF); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Instant and sorcery spells you control have lifelink. + this.addAbility(new SimpleStaticAbility(new GainAbilitySpellsEffect( + LifelinkAbility.getInstance(), filter + ).setText("instant and sorcery spells you control have lifelink"))); + + // At the beginning of your upkeep, exile an instant or sorcery card at random from your graveyard. You may cast it this turn. If a spell cast this way would be put into your graveyard, exile it instead. + this.addAbility(new BeginningOfUpkeepTriggeredAbility( + new RadiantScrollwielderEffect(), TargetController.YOU, false + ), new RadiantScrollwielderWatcher()); + } + + private RadiantScrollwielder(final RadiantScrollwielder card) { + super(card); + } + + @Override + public RadiantScrollwielder copy() { + return new RadiantScrollwielder(this); + } +} + +class RadiantScrollwielderEffect extends OneShotEffect { + + RadiantScrollwielderEffect() { + super(Outcome.Benefit); + staticText = "exile an instant or sorcery card at random from your graveyard. You may cast it this turn. " + + "If a spell cast this way would be put into your graveyard, exile it instead"; + } + + private RadiantScrollwielderEffect(final RadiantScrollwielderEffect effect) { + super(effect); + } + + @Override + public RadiantScrollwielderEffect copy() { + return new RadiantScrollwielderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null || player.getGraveyard().count(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, game) < 1) { + return false; + } + TargetCardInYourGraveyard target = new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY); + target.setNotTarget(true); + target.setRandom(true); + player.chooseTarget(outcome, target, source, game); + Card card = game.getCard(target.getFirstTarget()); + if (card == null) { + return false; + } + PlayFromNotOwnHandZoneTargetEffect.exileAndPlayFromExile( + game, source, card, TargetController.YOU, + Duration.EndOfTurn, false, true + ); + game.addEffect(new RadiantScrollwielderReplacementEffect(card, game), source); + return true; + } +} + +class RadiantScrollwielderReplacementEffect extends ReplacementEffectImpl { + + private final MageObjectReference mor; + + RadiantScrollwielderReplacementEffect(Card card, Game game) { + super(Duration.EndOfTurn, Outcome.Exile); + this.mor = new MageObjectReference(card, game, 1); + } + + private RadiantScrollwielderReplacementEffect(final RadiantScrollwielderReplacementEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public RadiantScrollwielderReplacementEffect copy() { + return new RadiantScrollwielderReplacementEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + ((ZoneChangeEvent) event).setToZone(Zone.EXILED); + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return ((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD + && mor.refersTo(game.getCard(event.getSourceId()), game) + && RadiantScrollwielderWatcher.checkSpell(game.getCard(event.getSourceId()), source, game); + } +} + +class RadiantScrollwielderWatcher extends Watcher { + + private final Map morMap = new HashMap<>(); + + RadiantScrollwielderWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.SPELL_CAST) { + return; + } + morMap.put( + new MageObjectReference(event.getSourceId(), game), + event.getAdditionalReference().getApprovingMageObjectReference() + ); + } + + @Override + public void reset() { + super.reset(); + this.morMap.clear(); + } + + static boolean checkSpell(Card card, Ability source, Game game) { + if (card == null) { + return false; + } + RadiantScrollwielderWatcher watcher = game.getState().getWatcher(RadiantScrollwielderWatcher.class); + if (watcher == null) { + return false; + } + MageObjectReference mor = watcher.morMap.getOrDefault(new MageObjectReference(card, game), null); + return mor != null && mor.refersTo(source.getSourceObject(game), game); + } +} diff --git a/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java b/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java index 834354526f8..5172c147570 100644 --- a/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java +++ b/Mage.Sets/src/mage/sets/StrixhavenSchoolOfMages.java @@ -218,6 +218,7 @@ public final class StrixhavenSchoolOfMages extends ExpansionSet { cards.add(new SetCardInfo("Quandrix Cultivator", 218, Rarity.UNCOMMON, mage.cards.q.QuandrixCultivator.class)); cards.add(new SetCardInfo("Quandrix Pledgemage", 219, Rarity.COMMON, mage.cards.q.QuandrixPledgemage.class)); cards.add(new SetCardInfo("Quintorius, Field Historian", 220, Rarity.UNCOMMON, mage.cards.q.QuintoriusFieldHistorian.class)); + cards.add(new SetCardInfo("Radiant Scrollwielder", 221, Rarity.RARE, mage.cards.r.RadiantScrollwielder.class)); cards.add(new SetCardInfo("Reckless Amplimancer", 141, Rarity.COMMON, mage.cards.r.RecklessAmplimancer.class)); cards.add(new SetCardInfo("Reconstruct History", 222, Rarity.UNCOMMON, mage.cards.r.ReconstructHistory.class)); cards.add(new SetCardInfo("Reduce to Memory", 25, Rarity.UNCOMMON, mage.cards.r.ReduceToMemory.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/stx/RadiantScrollwielderTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/stx/RadiantScrollwielderTest.java new file mode 100644 index 00000000000..60990fafb7d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/stx/RadiantScrollwielderTest.java @@ -0,0 +1,33 @@ +package org.mage.test.cards.single.stx; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class RadiantScrollwielderTest extends CardTestPlayerBase { + + private static final String wielder = "Radiant Scrollwielder"; + private static final String bolt = "Lightning Bolt"; + + @Test + public void testExileCastExile() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, wielder); + addCard(Zone.GRAVEYARD, playerA, bolt); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bolt, playerB); + +// setStrictChooseMode(true); currently doesn't work as computer player doesn't allow random targeting + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertLife(playerA, 20 + 3); + assertLife(playerB, 20 - 3); + assertExileCount(playerA, bolt, 1); + } +}