[LTR] Implement Stone of Erech (#10497)

* [LTR] Implement Stone of Erech

Grouped together two other recent cards for that exact same replacement effect.

* refactor some more

This does extend the ReplacementEffect "If ... would die, exile it instead", using a `PermanentFilter`. [[Void Maw]] and [[Lorcan, Warlock Collector]] have thus be refactored to use that instead of a custom replacement effect.

Added a static filter `StaticFilters.FILTER_ANOTHER_CREATURE` for Void Maw that is filtering "another creature".
Found and refactored cards that were declaring that exact filter locally
* [[Flame-Kin War Scout]]
* [[Herd Gnarr]]
* [[Mogg Bombers]]
* [[Timid Drake]]

* fix void maw

* reverse changes on VoidMaw

Void Maw was a linked ability, so not exactly the same replacement effect that was refactored.
This commit is contained in:
Susucre 2023-06-29 15:17:34 +02:00 committed by GitHub
parent 311bf2e22e
commit c92ad45e56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 167 additions and 184 deletions

View file

@ -10,13 +10,8 @@ import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -26,12 +21,6 @@ import mage.game.permanent.Permanent;
*/
public final class FlameKinWarScout extends CardImpl {
private static FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature");
static {
filter.add(AnotherPredicate.instance);
}
public FlameKinWarScout(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}");
@ -41,7 +30,11 @@ public final class FlameKinWarScout extends CardImpl {
this.toughness = new MageInt(4);
// When another creature enters the battlefield, sacrifice Flame-Kin War Scout. If you do, Flame-Kin War Scout deals 4 damage to that creature.
this.addAbility(new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new FlameKinWarScourEffect(), filter, false, SetTargetPointer.PERMANENT, null));
this.addAbility(new EntersBattlefieldAllTriggeredAbility(
Zone.BATTLEFIELD, new FlameKinWarScourEffect(),
StaticFilters.FILTER_ANOTHER_CREATURE, false,
SetTargetPointer.PERMANENT, null
));
}

View file

@ -1,4 +1,3 @@
package mage.cards.h;
import java.util.UUID;

View file

@ -9,21 +9,21 @@ import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.ReturnToHandTargetEffect;
import mage.constants.*;
import mage.abilities.effects.common.replacement.CreaturesAreExiledOnDeathReplacementEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.LifelinkAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.filter.predicate.permanent.TokenPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
/**
*
@ -57,7 +57,9 @@ public final class LiesaForgottenArchangel extends CardImpl {
this.addAbility(new DiesCreatureTriggeredAbility(new LiesaForgottenArchangelReturnToHandEffect(), false, filter, true));
// If a creature an opponent controls would die, exile it instead.
this.addAbility(new SimpleStaticAbility(new LiesaForgottenArchangelReplacementEffect()));
this.addAbility(new SimpleStaticAbility(
new CreaturesAreExiledOnDeathReplacementEffect(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)
));
}
private LiesaForgottenArchangel(final LiesaForgottenArchangel card) {
@ -96,44 +98,3 @@ class LiesaForgottenArchangelReturnToHandEffect extends OneShotEffect {
return true;
}
}
class LiesaForgottenArchangelReplacementEffect extends ReplacementEffectImpl {
public LiesaForgottenArchangelReplacementEffect() {
super(Duration.WhileOnBattlefield, Outcome.Exile);
staticText = "If a creature an opponent controls would die, exile it instead";
}
private LiesaForgottenArchangelReplacementEffect(final LiesaForgottenArchangelReplacementEffect effect) {
super(effect);
}
@Override
public LiesaForgottenArchangelReplacementEffect copy() {
return new LiesaForgottenArchangelReplacementEffect(this);
}
@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) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (zEvent.isDiesEvent()) {
Permanent permanent = zEvent.getTarget();
if (permanent != null && permanent.isCreature()) {
Player player = game.getPlayer(source.getControllerId());
return player != null && player.hasOpponent(permanent.getControllerId(), game);
}
}
return false;
}
}

View file

@ -7,18 +7,16 @@ import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.continuous.AddCardSubTypeTargetEffect;
import mage.abilities.effects.common.replacement.CreaturesAreExiledOnDeathReplacementEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
@ -48,7 +46,9 @@ public final class LorcanWarlockCollector extends CardImpl {
));
// If a Warlock you control would die, exile it instead.
this.addAbility(new SimpleStaticAbility(new LorcanWarlockCollectorReplacementEffect()));
this.addAbility(new SimpleStaticAbility(
new CreaturesAreExiledOnDeathReplacementEffect(new FilterControlledCreaturePermanent(SubType.WARLOCK))
));
}
private LorcanWarlockCollector(final LorcanWarlockCollector card) {
@ -95,45 +95,4 @@ class LorcanWarlockCollectorReturnEffect extends OneShotEffect {
player.moveCards(card, Zone.BATTLEFIELD, source, game);
return true;
}
}
class LorcanWarlockCollectorReplacementEffect extends ReplacementEffectImpl {
LorcanWarlockCollectorReplacementEffect() {
super(Duration.WhileOnBattlefield, Outcome.Exile);
staticText = "if a Warlock you control would die, exile it instead";
}
private LorcanWarlockCollectorReplacementEffect(final LorcanWarlockCollectorReplacementEffect effect) {
super(effect);
}
@Override
public LorcanWarlockCollectorReplacementEffect copy() {
return new LorcanWarlockCollectorReplacementEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent permanent = ((ZoneChangeEvent) event).getTarget();
if (permanent == null) {
return false;
}
Player player = game.getPlayer(permanent.getControllerId());
return player != null && player.moveCards(permanent, Zone.EXILED, source, game);
}
@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) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
return zEvent.getTarget() != null
&& zEvent.getTarget().isControlledBy(source.getControllerId())
&& zEvent.getTarget().hasSubtype(SubType.WARLOCK, game)
&& zEvent.isDiesEvent();
}
}
}

View file

@ -1,22 +1,19 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.continuous.BoostSourceEffect;
import mage.abilities.effects.common.replacement.CreaturesAreExiledOnDeathReplacementEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.StaticFilters;
/**
* @author PurpleCrowbar
@ -30,7 +27,9 @@ public final class MiserysShadow extends CardImpl {
this.toughness = new MageInt(2);
// If a creature an opponent controls would die, exile it instead.
this.addAbility(new SimpleStaticAbility(new MiserysShadowReplacementEffect()));
this.addAbility(new SimpleStaticAbility(
new CreaturesAreExiledOnDeathReplacementEffect(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)
));
// {1}: Misery's Shadow gets +1/+1 until end of turn.
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BoostSourceEffect(1, 1, Duration.EndOfTurn), new GenericManaCost(1)));
@ -45,44 +44,3 @@ public final class MiserysShadow extends CardImpl {
return new MiserysShadow(this);
}
}
class MiserysShadowReplacementEffect extends ReplacementEffectImpl {
public MiserysShadowReplacementEffect() {
super(Duration.WhileOnBattlefield, Outcome.Exile);
staticText = "If a creature an opponent controls would die, exile it instead";
}
private MiserysShadowReplacementEffect(final MiserysShadowReplacementEffect effect) {
super(effect);
}
@Override
public MiserysShadowReplacementEffect copy() {
return new MiserysShadowReplacementEffect(this);
}
@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) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (zEvent.isDiesEvent()) {
Permanent permanent = zEvent.getTarget();
if (permanent != null && permanent.isCreature()) {
Player player = game.getPlayer(source.getControllerId());
return player != null && player.hasOpponent(permanent.getControllerId(), game);
}
}
return false;
}
}

View file

@ -7,13 +7,12 @@ import mage.abilities.common.EntersBattlefieldAllTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.SacrificeSourceEffect;
import mage.constants.SubType;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.filter.StaticFilters;
import mage.target.TargetPlayer;
/**
@ -23,11 +22,6 @@ import mage.target.TargetPlayer;
public final class MoggBombers extends CardImpl {
private static final String rule = "When another creature enters the battlefield, sacrifice {this} and it deals 3 damage to target player.";
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature");
static {
filter.add(AnotherPredicate.instance);
}
public MoggBombers(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}");
@ -40,9 +34,11 @@ public final class MoggBombers extends CardImpl {
Effect sacrificeMoggBombers = new SacrificeSourceEffect();
Effect damageTargetPlayer = new DamageTargetEffect(3);
Ability ability = new EntersBattlefieldAllTriggeredAbility(
Zone.BATTLEFIELD,
sacrificeMoggBombers,
filter, false, rule);
Zone.BATTLEFIELD,
sacrificeMoggBombers,
StaticFilters.FILTER_ANOTHER_CREATURE,
false,
rule);
ability.addEffect(damageTargetPlayer);
ability.addTarget(new TargetPlayer());
this.addAbility(ability);

View file

@ -0,0 +1,54 @@
package mage.cards.s;
import java.util.UUID;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.ExileGraveyardAllTargetPlayerEffect;
import mage.abilities.effects.common.replacement.CreaturesAreExiledOnDeathReplacementEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SuperType;
import mage.filter.StaticFilters;
import mage.target.TargetPlayer;
/**
*
* @author Susucr
*/
public final class StoneOfErech extends CardImpl {
public StoneOfErech(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}");
this.supertype.add(SuperType.LEGENDARY);
// If a creature an opponent controls would die, exile it instead.
this.addAbility(new SimpleStaticAbility(
new CreaturesAreExiledOnDeathReplacementEffect(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)
));
// {2}, {T}, Sacrifice Stone of Erech: Exile target player's graveyard. Draw a card.
SimpleActivatedAbility ability = new SimpleActivatedAbility(
new ExileGraveyardAllTargetPlayerEffect(), new TapSourceCost()
);
ability.addEffect(new DrawCardSourceControllerEffect(1));
ability.addCost(new GenericManaCost(2));
ability.addCost(new SacrificeSourceCost());
ability.addTarget(new TargetPlayer());
this.addAbility(ability);
}
private StoneOfErech(final StoneOfErech card) {
super(card);
}
@Override
public StoneOfErech copy() {
return new StoneOfErech(this);
}
}

View file

@ -1,18 +1,16 @@
package mage.cards.t;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldAllTriggeredAbility;
import mage.abilities.effects.common.ReturnToHandSourceEffect;
import mage.constants.SubType;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.filter.StaticFilters;
/**
*
@ -20,12 +18,6 @@ import mage.filter.predicate.mageobject.AnotherPredicate;
*/
public final class TimidDrake extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature");
static {
filter.add(AnotherPredicate.instance);
}
public TimidDrake(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}");
@ -38,7 +30,8 @@ public final class TimidDrake extends CardImpl {
// When another creature enters the battlefield, return Timid Drake to its owner's hand.
this.addAbility(new EntersBattlefieldAllTriggeredAbility(
Zone.BATTLEFIELD, new ReturnToHandSourceEffect(true), filter, false,
Zone.BATTLEFIELD, new ReturnToHandSourceEffect(true),
StaticFilters.FILTER_ANOTHER_CREATURE, false,
"When another creature enters the battlefield, return {this} to its owner's hand."
));
}

View file

@ -240,6 +240,7 @@ public final class TheLordOfTheRingsTalesOfMiddleEarth extends ExpansionSet {
cards.add(new SetCardInfo("Stalwarts of Osgiliath", 33, Rarity.COMMON, mage.cards.s.StalwartsOfOsgiliath.class));
cards.add(new SetCardInfo("Stern Scolding", 71, Rarity.UNCOMMON, mage.cards.s.SternScolding.class));
cards.add(new SetCardInfo("Stew the Coneys", 189, Rarity.UNCOMMON, mage.cards.s.StewTheConeys.class));
cards.add(new SetCardInfo("Stone of Erech", 251, Rarity.UNCOMMON, mage.cards.s.StoneOfErech.class));
cards.add(new SetCardInfo("Storm of Saruman", 72, Rarity.MYTHIC, mage.cards.s.StormOfSaruman.class));
cards.add(new SetCardInfo("Surrounded by Orcs", 73, Rarity.COMMON, mage.cards.s.SurroundedByOrcs.class));
cards.add(new SetCardInfo("Swamp", 266, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS));

View file

@ -0,0 +1,62 @@
package mage.abilities.effects.common.replacement;
import mage.abilities.Ability;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.util.CardUtil;
/**
* @author Susucr
*/
public class CreaturesAreExiledOnDeathReplacementEffect extends ReplacementEffectImpl {
private FilterPermanent filter;
public CreaturesAreExiledOnDeathReplacementEffect(FilterPermanent filter) {
super(Duration.WhileOnBattlefield, Outcome.Exile);
staticText = "If " + CardUtil.addArticle(filter.getMessage()) + " would die, exile it instead";
}
private CreaturesAreExiledOnDeathReplacementEffect(final CreaturesAreExiledOnDeathReplacementEffect effect) {
super(effect);
this.filter = effect.filter;
}
@Override
public CreaturesAreExiledOnDeathReplacementEffect copy() {
return new CreaturesAreExiledOnDeathReplacementEffect(this);
}
@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) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (!zEvent.isDiesEvent()) {
return false;
}
Permanent permanent = zEvent.getTarget();
if (permanent == null) {
return false;
}
return filter.match(permanent, source.getControllerId(), source, game);
}
}

View file

@ -560,6 +560,13 @@ public final class StaticFilters {
FILTER_OPPONENTS_PERMANENT_ARTIFACT_OR_CREATURE.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_ANOTHER_CREATURE = new FilterCreaturePermanent("another creature");
static {
FILTER_ANOTHER_CREATURE.add(AnotherPredicate.instance);
FILTER_ANOTHER_CREATURE.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_ANOTHER_TARGET_CREATURE = new FilterCreaturePermanent("another target creature");
static {