refactor effects "you may cast... from... graveyard... exile it instead" (#10926)

* cleanup exiling cast spells

* common class MayCastTargetThenExileEffect

* fix zcc check

* add test suite
This commit is contained in:
xenohedron 2023-08-21 00:26:09 -04:00 committed by GitHub
parent 570b47705a
commit 4af977289e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 461 additions and 1252 deletions

View file

@ -4,13 +4,15 @@ import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.effects.*;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.effects.common.SacrificeTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.effects.common.counter.AddCountersAllEffect;
import mage.abilities.keyword.HasteAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
@ -22,8 +24,6 @@ import mage.filter.common.FilterInstantOrSorceryCard;
import mage.filter.predicate.mageobject.ColorPredicate;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.RedElementalToken;
import mage.game.permanent.token.Token;
@ -33,14 +33,14 @@ import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
* @author TheElk801
* @author TheElk801, xenohedron
*/
public final class ChandraAcolyteOfFlame extends CardImpl {
private static final FilterPermanent filter
= new FilterControlledPlaneswalkerPermanent("red planeswalker you control");
private static final FilterCard filter2
= new FilterInstantOrSorceryCard("instant or sorcery card with mana value 3 or less");
= new FilterInstantOrSorceryCard("instant or sorcery card with mana value 3 or less from your graveyard");
static {
filter.add(new ColorPredicate(ObjectColor.RED));
@ -61,7 +61,7 @@ public final class ChandraAcolyteOfFlame extends CardImpl {
this.addAbility(new LoyaltyAbility(new ChandraAcolyteOfFlameEffect(), 0));
// -2: You may cast target instant or sorcery card with converted mana cost 3 or less from your graveyard. If that card would be put into your graveyard this turn, exile it instead.
Ability ability = new LoyaltyAbility(new ChandraAcolyteOfFlameGraveyardEffect(), -2);
Ability ability = new LoyaltyAbility(new MayCastTargetThenExileEffect(false), -2);
ability.addTarget(new TargetCardInYourGraveyard(filter2));
this.addAbility(ability);
}
@ -119,101 +119,3 @@ class ChandraAcolyteOfFlameEffect extends OneShotEffect {
return true;
}
}
class ChandraAcolyteOfFlameGraveyardEffect extends OneShotEffect {
ChandraAcolyteOfFlameGraveyardEffect() {
super(Outcome.Benefit);
this.staticText = "You may cast target instant or sorcery card " +
"with mana value 3 or less from your graveyard this turn. " +
"If that card would be put into your graveyard this turn, exile it instead";
}
private ChandraAcolyteOfFlameGraveyardEffect(final ChandraAcolyteOfFlameGraveyardEffect effect) {
super(effect);
}
@Override
public ChandraAcolyteOfFlameGraveyardEffect copy() {
return new ChandraAcolyteOfFlameGraveyardEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(this.getTargetPointer().getFirst(game, source));
if (card != null) {
ContinuousEffect effect = new ChandraAcolyteOfFlameCastFromGraveyardEffect();
effect.setTargetPointer(new FixedTarget(card, game));
game.addEffect(effect, source);
effect = new ChandraAcolyteOfFlameReplacementEffect(card.getId());
game.addEffect(effect, source);
return true;
}
return false;
}
}
class ChandraAcolyteOfFlameCastFromGraveyardEffect extends AsThoughEffectImpl {
ChandraAcolyteOfFlameCastFromGraveyardEffect() {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit);
}
private ChandraAcolyteOfFlameCastFromGraveyardEffect(final ChandraAcolyteOfFlameCastFromGraveyardEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public ChandraAcolyteOfFlameCastFromGraveyardEffect copy() {
return new ChandraAcolyteOfFlameCastFromGraveyardEffect(this);
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
return objectId.equals(this.getTargetPointer().getFirst(game, source)) && affectedControllerId.equals(source.getControllerId());
}
}
class ChandraAcolyteOfFlameReplacementEffect extends ReplacementEffectImpl {
private final UUID cardId;
ChandraAcolyteOfFlameReplacementEffect(UUID cardId) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardId = cardId;
staticText = "If that card would be put into your graveyard this turn, exile it instead";
}
private ChandraAcolyteOfFlameReplacementEffect(final ChandraAcolyteOfFlameReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public ChandraAcolyteOfFlameReplacementEffect copy() {
return new ChandraAcolyteOfFlameReplacementEffect(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;
return zEvent.getToZone() == Zone.GRAVEYARD
&& zEvent.getTargetId().equals(this.cardId);
}
}

View file

@ -3,10 +3,10 @@ package mage.cards.c;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.effects.CastCardFromGraveyardThenExileItEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamagePlayersEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.effects.common.continuous.CastFromHandWithoutPayingManaCostEffect;
import mage.abilities.effects.common.discard.DiscardHandControllerEffect;
import mage.cards.CardImpl;
@ -14,23 +14,21 @@ import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.predicate.Predicates;
import mage.filter.common.FilterInstantOrSorceryCard;
import mage.filter.predicate.mageobject.ColorPredicate;
import mage.target.common.TargetCardInYourGraveyard;
import java.util.UUID;
/**
*
* @author htrajan
* @author htrajan, xenohedron
*/
public final class ChandraFlamesCatalyst extends CardImpl {
private static final FilterCard filter = new FilterCard();
private static final FilterCard filter = new FilterInstantOrSorceryCard("red instant or sorcery card from your graveyard");
static {
filter.add(new ColorPredicate(ObjectColor.RED));
filter.add(Predicates.or(CardType.INSTANT.getPredicate(), CardType.SORCERY.getPredicate()));
}
public ChandraFlamesCatalyst(UUID ownerId, CardSetInfo setInfo) {
@ -44,9 +42,7 @@ public final class ChandraFlamesCatalyst extends CardImpl {
this.addAbility(new LoyaltyAbility(new DamagePlayersEffect(3, TargetController.OPPONENT), 1));
// 2: You may cast target red instant or sorcery card from your graveyard. If that spell would be put into your graveyard this turn, exile it instead.
CastCardFromGraveyardThenExileItEffect minusEffect = new CastCardFromGraveyardThenExileItEffect();
minusEffect.setText("You may cast target red instant or sorcery card from your graveyard. If that spell would be put into your graveyard this turn, exile it instead");
Ability ability = new LoyaltyAbility(minusEffect, -2);
Ability ability = new LoyaltyAbility(new MayCastTargetThenExileEffect(false), -2);
ability.addTarget(new TargetCardInYourGraveyard(filter));
this.addAbility(ability);

View file

@ -1,16 +1,12 @@
package mage.cards.d;
import mage.ApprovingObject;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleEvasionAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesSourceEffect;
import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect;
import mage.abilities.keyword.CrewAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
@ -21,15 +17,14 @@ import mage.filter.predicate.card.OwnerIdPredicate;
import mage.game.Game;
import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInGraveyard;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
* @author TheElk801
* @author TheElk801, Grath, xenohedron
*/
public final class DeluxeDragster extends CardImpl {
@ -69,11 +64,15 @@ public final class DeluxeDragster extends CardImpl {
class DeluxeDragsterTriggeredAbility extends TriggeredAbilityImpl {
public DeluxeDragsterTriggeredAbility() {
super(Zone.BATTLEFIELD, new DeluxeDragsterEffect(), true);
DeluxeDragsterTriggeredAbility() {
super(Zone.BATTLEFIELD, new MayCastTargetThenExileEffect(true)
.setText("you may cast target instant or sorcery card from "
+ "that player's graveyard without paying its mana cost. "
+ ThatSpellGraveyardExileReplacementEffect.RULE_A), false);
setTriggerPhrase("Whenever {this} deals combat damage to a player, ");
}
public DeluxeDragsterTriggeredAbility(final DeluxeDragsterTriggeredAbility ability) {
private DeluxeDragsterTriggeredAbility(final DeluxeDragsterTriggeredAbility ability) {
super(ability);
}
@ -96,102 +95,15 @@ class DeluxeDragsterTriggeredAbility extends TriggeredAbilityImpl {
if (damagedPlayer == null) {
return false;
}
FilterCard filter = new FilterCard("instant or sorcery in " + damagedPlayer.getName() + "'s graveyard");
filter.add(Predicates.or(CardType.INSTANT.getPredicate(), CardType.SORCERY.getPredicate()));
FilterCard filter = new FilterCard("instant or sorcery card from that player's graveyard");
filter.add(new OwnerIdPredicate(damagedPlayer.getId()));
TargetCardInGraveyard target = new TargetCardInGraveyard(filter);
filter.add(Predicates.or(
CardType.INSTANT.getPredicate(),
CardType.SORCERY.getPredicate()
));
Target target = new TargetCardInGraveyard(filter);
this.getTargets().clear();
this.addTarget(target);
return true;
}
@Override
public String getRule() {
return "Whenever {this} deals combat damage to a player, "
+ "you may cast target instant or sorcery card from "
+ "that player's graveyard without paying its mana "
+ "cost. If that spell would be put into a graveyard, "
+ "exile it instead.";
}
}
class DeluxeDragsterEffect extends OneShotEffect {
public DeluxeDragsterEffect() {
super(Outcome.PlayForFree);
this.staticText = "you may cast target instant or sorcery card from "
+ "that player's graveyard without paying its mana "
+ "cost. If that spell would be put into a graveyard, "
+ "exile it instead";
}
public DeluxeDragsterEffect(final DeluxeDragsterEffect effect) {
super(effect);
}
@Override
public DeluxeDragsterEffect copy() {
return new DeluxeDragsterEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Card targetCard = game.getCard(source.getFirstTarget());
if (targetCard != null) {
game.getState().setValue("PlayFromNotOwnHandZone" + targetCard.getId(), Boolean.TRUE);
Boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(targetCard, game, true),
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + targetCard.getId(), null);
if (cardWasCast) {
ContinuousEffect effect = new DeluxeDragsterReplacementEffect();
effect.setTargetPointer(new FixedTarget(targetCard.getId(), game.getState().getZoneChangeCounter(targetCard.getId())));
game.addEffect(effect, source);
}
}
return true;
}
return false;
}
}
class DeluxeDragsterReplacementEffect extends ReplacementEffectImpl {
public DeluxeDragsterReplacementEffect() {
super(Duration.EndOfTurn, Outcome.Exile);
staticText = "If that spell would be put into a graveyard this turn, exile it instead";
}
public DeluxeDragsterReplacementEffect(final DeluxeDragsterReplacementEffect effect) {
super(effect);
}
@Override
public DeluxeDragsterReplacementEffect copy() {
return new DeluxeDragsterReplacementEffect(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) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
return zEvent.getToZone() == Zone.GRAVEYARD
&& event.getTargetId().equals(getTargetPointer().getFirst(game, source));
}
}

View file

@ -5,7 +5,7 @@ import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
@ -14,8 +14,6 @@ import mage.constants.*;
import mage.filter.FilterCard;
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.TargetCardInOpponentsGraveyard;
import mage.target.targetpointer.FixedTarget;
@ -67,14 +65,14 @@ public final class DireFleetDaredevil extends CardImpl {
class DireFleetDaredevilEffect extends OneShotEffect {
public DireFleetDaredevilEffect() {
DireFleetDaredevilEffect() {
super(Outcome.Benefit);
this.staticText = "exile target instant or sorcery card from an opponent's graveyard. " +
"You may cast it this turn, and you may spend mana as though it were mana of any type " +
"to cast that spell. If that spell would be put into a graveyard this turn, exile it instead";
"You may cast it this turn, and mana of any type can be spent to cast that spell. "
+ ThatSpellGraveyardExileReplacementEffect.RULE_A;
}
public DireFleetDaredevilEffect(final DireFleetDaredevilEffect effect) {
private DireFleetDaredevilEffect(final DireFleetDaredevilEffect effect) {
super(effect);
}
@ -86,64 +84,22 @@ class DireFleetDaredevilEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Card targetCard = game.getCard(getTargetPointer().getFirst(game, source));
if (targetCard != null) {
if (controller.moveCards(targetCard, Zone.EXILED, source, game)) {
targetCard = game.getCard(targetCard.getId());
if (targetCard != null) {
// you may play and spend any mana
CardUtil.makeCardPlayable(game, source, targetCard, Duration.EndOfTurn, true);
// exile from graveyard
ContinuousEffect effect = new DireFleetDaredevilReplacementEffect();
effect.setTargetPointer(new FixedTarget(targetCard, game));
game.addEffect(effect, source);
return true;
}
}
Card targetCard = game.getCard(getTargetPointer().getFirst(game, source));
if (controller == null || targetCard == null) {
return false;
}
if (controller.moveCards(targetCard, Zone.EXILED, source, game)) {
Card card = game.getCard(targetCard.getId());
if (card != null) {
// you may play and spend any mana
CardUtil.makeCardPlayable(game, source, card, Duration.EndOfTurn, true);
// exile from graveyard
ContinuousEffect effect = new ThatSpellGraveyardExileReplacementEffect(false);
effect.setTargetPointer(new FixedTarget(card, game));
game.addEffect(effect, source);
return true;
}
}
return false;
}
}
class DireFleetDaredevilReplacementEffect extends ReplacementEffectImpl {
public DireFleetDaredevilReplacementEffect() {
super(Duration.EndOfTurn, Outcome.Exile);
staticText = "If that card would be put into a graveyard this turn, exile it instead";
}
public DireFleetDaredevilReplacementEffect(final DireFleetDaredevilReplacementEffect effect) {
super(effect);
}
@Override
public DireFleetDaredevilReplacementEffect copy() {
return new DireFleetDaredevilReplacementEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Card card = game.getCard(event.getTargetId());
Player controller = game.getPlayer(source.getControllerId());
if (card != null && controller != null) {
return controller.moveCards(card, Zone.EXILED, source, game);
}
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;
return zEvent.getToZone() == Zone.GRAVEYARD
&& event.getTargetId().equals(((FixedTarget) getTargetPointer()).getTarget())
&& ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1
== game.getState().getZoneChangeCounter(event.getTargetId());
}
}

View file

@ -3,36 +3,30 @@ package mage.cards.d;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.common.FilterInstantOrSorceryCard;
import mage.filter.predicate.ObjectSourcePlayer;
import mage.filter.predicate.ObjectSourcePlayerPredicate;
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.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
import mage.ApprovingObject;
/**
* @author TheElk801
* @author TheElk801, xenohedron
*/
public final class DreadhordeArcanist extends CardImpl {
private static final FilterCard filter = new FilterInstantOrSorceryCard(
"instant or sorcery card with mana value less than or equal to this creature's power"
"instant or sorcery card with mana value less than or equal to {this}'s power from your graveyard"
);
static {
@ -50,11 +44,9 @@ public final class DreadhordeArcanist extends CardImpl {
// Trample
this.addAbility(TrampleAbility.getInstance());
// Whenever Dreadhorde Arcanist attacks, you may cast target instant or
// sorcery card with converted mana cost less than or equal to Dreadhorde Arcanist's
// power from your graveyard without paying its mana cost. If that card would be put
// into your graveyard this turn, exile it instead.
Ability ability = new AttacksTriggeredAbility(new DreadhordeArcanistEffect(), false);
// Whenever Dreadhorde Arcanist attacks, you may cast target instant or sorcery card with converted mana cost less than or equal to Dreadhorde Arcanist's power from your graveyard without paying its mana cost.
// If that card would be put into your graveyard this turn, exile it instead.
Ability ability = new AttacksTriggeredAbility(new MayCastTargetThenExileEffect(true), false);
ability.addTarget(new TargetCardInYourGraveyard(filter));
this.addAbility(ability);
}
@ -79,81 +71,3 @@ enum DreadhordeArcanistPredicate implements ObjectSourcePlayerPredicate<Card> {
&& input.getObject().getManaValue() <= sourcePermanent.getPower().getValue();
}
}
class DreadhordeArcanistEffect extends OneShotEffect {
DreadhordeArcanistEffect() {
super(Outcome.PlayForFree);
this.staticText = "you may cast target instant or sorcery card with mana value "
+ "less than or equal to {this}'s power from your graveyard without paying its mana cost. "
+ "If that spell would be put into your graveyard this turn, exile it instead.";
}
private DreadhordeArcanistEffect(final DreadhordeArcanistEffect effect) {
super(effect);
}
@Override
public DreadhordeArcanistEffect copy() {
return new DreadhordeArcanistEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Card card = game.getCard(this.getTargetPointer().getFirst(game, source));
if (card != null
&& controller.chooseUse(Outcome.PlayForFree, "Cast " + card.getLogName() + '?', source, game)) {
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, true),
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
}
ContinuousEffect effect = new DreadhordeArcanistReplacementEffect(card.getId());
effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId())));
game.addEffect(effect, source);
return true;
}
}
class DreadhordeArcanistReplacementEffect extends ReplacementEffectImpl {
private final UUID cardId;
DreadhordeArcanistReplacementEffect(UUID cardId) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardId = cardId;
staticText = "If that card would be put into your graveyard this turn, exile it instead";
}
private DreadhordeArcanistReplacementEffect(final DreadhordeArcanistReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public DreadhordeArcanistReplacementEffect copy() {
return new DreadhordeArcanistReplacementEffect(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;
return zEvent.getToZone() == Zone.GRAVEYARD
&& zEvent.getTargetId().equals(this.cardId);
}
}

View file

@ -1,28 +1,21 @@
package mage.cards.e;
import mage.ApprovingObject;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileCardEnteringGraveyardReplacementEffect;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.keyword.DoubleStrikeAbility;
import mage.cards.Card;
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 mage.target.common.TargetCardInYourGraveyard;
import java.util.UUID;
/**
*
* @author htrajan
* @author xenohedron
*/
public final class EfreetFlamepainter extends CardImpl {
@ -38,8 +31,8 @@ public final class EfreetFlamepainter extends CardImpl {
this.addAbility(DoubleStrikeAbility.getInstance());
// Whenever Efreet Flamepainter deals combat damage to a player, you may cast target instant or sorcery card from your graveyard without paying its mana cost. If that spell would be put into your graveyard, exile it instead.
Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new EfreetFlamepainterEffect(), false);
ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY));
Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new MayCastTargetThenExileEffect(true), false);
ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD));
this.addAbility(ability);
}
@ -52,37 +45,3 @@ public final class EfreetFlamepainter extends CardImpl {
return new EfreetFlamepainter(this);
}
}
class EfreetFlamepainterEffect extends OneShotEffect {
EfreetFlamepainterEffect() {
super(Outcome.PlayForFree);
staticText = "you may cast target instant or sorcery card from your graveyard without paying its mana cost. If that spell would be put into your graveyard, exile it instead";
}
EfreetFlamepainterEffect(EfreetFlamepainterEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
UUID targetId = source.getFirstTarget();
if (targetId != null) {
Card card = game.getCard(targetId);
if (card != null && controller.chooseUse(outcome, "Cast " + card.getName() + " without paying its mana cost?", source, game)) {
game.addEffect(new ExileCardEnteringGraveyardReplacementEffect(card.getId()), source);
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, true),
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
}
}
return true;
}
@Override
public EfreetFlamepainterEffect copy() {
return new EfreetFlamepainterEffect(this);
}
}

View file

@ -1,40 +1,28 @@
package mage.cards.g;
import java.util.UUID;
import mage.ApprovingObject;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.keyword.MenaceAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterInstantOrSorceryCard;
import mage.filter.predicate.mageobject.ManaValuePredicate;
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.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
*
* @author fireshoes
* @author xenohedron
*/
public final class GoblinDarkDwellers extends CardImpl {
private static final FilterInstantOrSorceryCard filter
= new FilterInstantOrSorceryCard("instant or sorcery card with mana value 3 or less");
= new FilterInstantOrSorceryCard("instant or sorcery card with mana value 3 or less from your graveyard");
static {
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4));
@ -49,9 +37,9 @@ public final class GoblinDarkDwellers extends CardImpl {
// Menace
this.addAbility(new MenaceAbility(false));
// When Goblin Dark-Dwellers enters the battlefield, you may cast target instant or sorcery card with converted mana cost 3 or less
// from your graveyard without paying its mana cost. If that card would be put into your graveyard this turn, exile it instead.
Ability ability = new EntersBattlefieldTriggeredAbility(new GoblinDarkDwellersEffect());
// When Goblin Dark-Dwellers enters the battlefield, you may cast target instant or sorcery card with converted mana cost 3 or less from your graveyard without paying its mana cost.
// If that card would be put into your graveyard this turn, exile it instead.
Ability ability = new EntersBattlefieldTriggeredAbility(new MayCastTargetThenExileEffect(true));
ability.addTarget(new TargetCardInYourGraveyard(filter));
this.addAbility(ability);
}
@ -65,84 +53,3 @@ public final class GoblinDarkDwellers extends CardImpl {
return new GoblinDarkDwellers(this);
}
}
class GoblinDarkDwellersEffect extends OneShotEffect {
GoblinDarkDwellersEffect() {
super(Outcome.PlayForFree);
this.staticText = "you may cast target instant or sorcery card with "
+ "mana value 3 or less from your graveyard without paying its mana cost. "
+ "If that spell would be put into your graveyard, exile it instead";
}
GoblinDarkDwellersEffect(final GoblinDarkDwellersEffect effect) {
super(effect);
}
@Override
public GoblinDarkDwellersEffect copy() {
return new GoblinDarkDwellersEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Card card = game.getCard(this.getTargetPointer().getFirst(game, source));
if (card != null) {
if (controller.chooseUse(Outcome.PlayForFree, "Cast " + card.getLogName() + '?', source, game)) {
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(card, game, true),
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
if (cardWasCast) {
ContinuousEffect effect = new GoblinDarkDwellersReplacementEffect(card.getId());
effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId())));
game.addEffect(effect, source);
}
}
}
return true;
}
return false;
}
}
class GoblinDarkDwellersReplacementEffect extends ReplacementEffectImpl {
private final UUID cardId;
GoblinDarkDwellersReplacementEffect(UUID cardId) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardId = cardId;
staticText = "If that card would be put into your graveyard this turn, exile it instead";
}
GoblinDarkDwellersReplacementEffect(final GoblinDarkDwellersReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public GoblinDarkDwellersReplacementEffect copy() {
return new GoblinDarkDwellersReplacementEffect(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;
return zEvent.getToZone() == Zone.GRAVEYARD
&& zEvent.getTargetId().equals(this.cardId);
}
}

View file

@ -1,30 +1,27 @@
package mage.cards.h;
import mage.MageInt;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.CardsImpl;
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.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
import mage.target.common.TargetCardInGraveyard;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.UUID;
@ -64,8 +61,8 @@ class HaloForagerPayEffect extends OneShotEffect {
HaloForagerPayEffect() {
super(Outcome.Benefit);
staticText = "you may pay {X}. When you do, you may cast target instant or sorcery card " +
"with mana value X from a graveyard without paying its mana cost. " +
"If that spell would be put into a graveyard, exile it instead.";
"with mana value X from a graveyard without paying its mana cost. "
+ ThatSpellGraveyardExileReplacementEffect.RULE_A;
}
private HaloForagerPayEffect(final HaloForagerPayEffect effect) {
@ -105,7 +102,7 @@ class HaloForagerCastEffect extends OneShotEffect {
HaloForagerCastEffect(int costX) {
super(Outcome.Benefit);
staticText = "You may cast target instant or sorcery card with mana value " + costX + " from a graveyard " +
"without paying its mana cost. If that spell would be put into a graveyard, exile it instead";
"without paying its mana cost. " + ThatSpellGraveyardExileReplacementEffect.RULE_A;
}
private HaloForagerCastEffect(final HaloForagerCastEffect effect) {
@ -121,65 +118,12 @@ class HaloForagerCastEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Card card = game.getCard(getTargetPointer().getFirst(game, source));
return player != null && card != null
&& CardUtil.castSpellWithAttributesForFree(
player, source, game, new CardsImpl(card),
StaticFilters.FILTER_CARD, HaloForagerTracker.instance
);
}
}
enum HaloForagerTracker implements CardUtil.SpellCastTracker {
instance;
@Override
public boolean checkCard(Card card, Game game) {
return true;
}
@Override
public void addCard(Card card, Ability source, Game game) {
game.addEffect(new HaloForagerReplacementEffect(card, game), source);
}
}
class HaloForagerReplacementEffect extends ReplacementEffectImpl {
private final MageObjectReference mor;
HaloForagerReplacementEffect(Card card, Game game) {
super(Duration.EndOfTurn, Outcome.Exile);
this.mor = new MageObjectReference(card.getMainCard(), game);
}
private HaloForagerReplacementEffect(final HaloForagerReplacementEffect effect) {
super(effect);
this.mor = effect.mor;
}
@Override
public HaloForagerReplacementEffect copy() {
return new HaloForagerReplacementEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
Card card = mor.getCard(game);
return controller != null
&& card != null
&& controller.moveCards(card, 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.getToZone() == Zone.GRAVEYARD
&& zEvent.getTargetId().equals(mor.getSourceId());
if (player == null || card == null) {
return false;
}
ContinuousEffect effect = new ThatSpellGraveyardExileReplacementEffect(false);
effect.setTargetPointer(new FixedTarget(card, game));
game.addEffect(effect, source);
return CardUtil.castSpellWithAttributesForFree(player, source, game, card);
}
}

View file

@ -5,6 +5,7 @@ import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.effects.*;
import mage.abilities.effects.common.GetEmblemEffect;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -12,7 +13,7 @@ import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.common.FilterInstantOrSorceryCard;
import mage.filter.StaticFilters;
import mage.game.command.emblems.JaceTelepathUnboundEmblem;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.common.TargetCreaturePermanent;
@ -41,10 +42,8 @@ public final class JaceTelepathUnbound extends CardImpl {
this.addAbility(ability);
// -3: You may cast target instant or sorcery card from your graveyard this turn. If that card would be put into your graveyard this turn, exile it instead.
CastCardFromGraveyardThenExileItEffect minusEffect = new CastCardFromGraveyardThenExileItEffect();
minusEffect.setText("You may cast target instant or sorcery card from your graveyard this turn. If that spell would be put into your graveyard, exile it instead");
ability = new LoyaltyAbility(minusEffect, -3);
ability.addTarget(new TargetCardInYourGraveyard(new FilterInstantOrSorceryCard()));
ability = new LoyaltyAbility(new MayCastTargetThenExileEffect(Duration.EndOfTurn), -3);
ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD));
this.addAbility(ability);
// 9: You get an emblem with "Whenever you cast a spell, target opponent mills five cards."

View file

@ -6,7 +6,7 @@ import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.common.LimitedTimesPerTurnActivatedAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.CastCardFromGraveyardThenExileItEffect;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.Card;
@ -61,10 +61,10 @@ public final class MavindaStudentsAdvocate extends CardImpl {
}
}
class MavindaStudentsAdvocateEffect extends CastCardFromGraveyardThenExileItEffect {
class MavindaStudentsAdvocateEffect extends MayCastTargetThenExileEffect {
MavindaStudentsAdvocateEffect() {
super();
super(Duration.EndOfTurn);
staticText = "you may cast target instant or sorcery card from your graveyard this turn. " +
"If that spell doesn't target a creature you control, it costs {8} more to cast this way. " +
"If that spell would be put into your graveyard, exile it instead";

View file

@ -1,30 +1,26 @@
package mage.cards.m;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect;
import mage.cards.Card;
import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.common.FilterInstantOrSorceryCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
*
* @author TheElk801
* @author TheElk801, xenohedron
*/
public final class MissionBriefing extends CardImpl {
@ -47,15 +43,14 @@ public final class MissionBriefing extends CardImpl {
class MissionBriefingEffect extends OneShotEffect {
public MissionBriefingEffect() {
MissionBriefingEffect() {
super(Outcome.Benefit);
this.staticText = "Surveil 2, then choose an instant or sorcery card "
+ "in your graveyard. You may cast that card this turn. "
+ "If that card would be put into your graveyard this turn, "
+ "exile it instead";
+ "in your graveyard. You may cast it this turn. "
+ ThatSpellGraveyardExileReplacementEffect.RULE_YOUR;
}
public MissionBriefingEffect(final MissionBriefingEffect effect) {
private MissionBriefingEffect(final MissionBriefingEffect effect) {
super(effect);
}
@ -71,65 +66,11 @@ class MissionBriefingEffect extends OneShotEffect {
return false;
}
player.surveil(2, source, game);
Target target = new TargetCardInYourGraveyard(
new FilterInstantOrSorceryCard("instant or sorcery card from your graveyard"));
if (!player.choose(outcome, target, source, game)) {
return true;
}
Card card = game.getCard(target.getFirstTarget());
if (card != null) {
ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect();
effect.setTargetPointer(new FixedTarget(card, game));
game.addEffect(effect, source);
effect = new MissionBriefingReplacementEffect(card.getId());
game.addEffect(effect, source);
return true;
}
return false;
}
}
class MissionBriefingReplacementEffect extends ReplacementEffectImpl {
private final UUID cardId;
public MissionBriefingReplacementEffect(UUID cardId) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardId = cardId;
staticText = "If that card would be put into your graveyard this turn, "
+ "exile it instead";
}
public MissionBriefingReplacementEffect(final MissionBriefingReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public MissionBriefingReplacementEffect copy() {
return new MissionBriefingReplacementEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
Card card = game.getCard(this.cardId);
if (controller != null && card != null) {
controller.moveCardsToExile(card, source, game, true, null, "");
return true;
}
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;
return zEvent.getToZone() == Zone.GRAVEYARD
&& zEvent.getTargetId().equals(this.cardId);
Target target = new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD);
player.choose(outcome, target, source, game);
Effect effect = new MayCastTargetThenExileEffect(Duration.EndOfTurn);
effect.setTargetPointer(new FixedTarget(target.getFirstTarget(), game));
effect.apply(game, source);
return true;
}
}

View file

@ -8,8 +8,8 @@ import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
@ -18,8 +18,6 @@ import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.common.FilterInstantOrSorceryCard;
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.target.targetpointer.FixedTarget;
@ -64,9 +62,10 @@ class OgreBattlecasterEffect extends OneShotEffect {
OgreBattlecasterEffect() {
super(Outcome.PlayForFree);
this.staticText = "you may cast target instant or sorcery card from your graveyard " +
"by paying {R}{R} in addition to its other costs. If that spell would be put into a graveyard, exile it instead. " +
"When you cast that spell, {this} gets +X/+0 until end of turn, where X is that spell's mana value";
this.staticText = "you may cast target instant or sorcery card from your graveyard "
+ "by paying {R}{R} in addition to its other costs. "
+ ThatSpellGraveyardExileReplacementEffect.RULE_A
+ " When you cast that spell, {this} gets +X/+0 until end of turn, where X is that spell's mana value";
}
private OgreBattlecasterEffect(final OgreBattlecasterEffect effect) {
@ -81,7 +80,7 @@ class OgreBattlecasterEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Card card = game.getCard(this.getTargetPointer().getFirst(game, source));
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (controller == null || card == null) {
return false;
}
@ -98,49 +97,10 @@ class OgreBattlecasterEffect extends OneShotEffect {
game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility(effect, false,
"When you cast that spell, {this} gets +X/+0 until end of turn, where X is that spell's mana value"), source);
}
ContinuousEffect effect = new ThatSpellGraveyardExileReplacementEffect(false);
effect.setTargetPointer(new FixedTarget(card, game));
game.addEffect(effect, source);
}
ContinuousEffect effect = new OgreBattlecasterReplacementEffect(card.getId());
effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId())));
game.addEffect(effect, source);
return true;
}
}
class OgreBattlecasterReplacementEffect extends ReplacementEffectImpl {
private final UUID cardId;
OgreBattlecasterReplacementEffect(UUID cardId) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardId = cardId;
staticText = "if that spell would be put into a graveyard, exile it instead";
}
private OgreBattlecasterReplacementEffect(final OgreBattlecasterReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public OgreBattlecasterReplacementEffect copy() {
return new OgreBattlecasterReplacementEffect(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;
return zEvent.getToZone() == Zone.GRAVEYARD
&& zEvent.getTargetId().equals(this.cardId);
}
}

View file

@ -1,10 +1,9 @@
package mage.cards.s;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileSourceEffect;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -39,7 +38,7 @@ public final class ShredsOfSanity extends CardImpl {
this.getSpellAbility().addEffect(new ShredsOfSanityEffect());
this.getSpellAbility().addTarget(new TargetCard(0, 1, Zone.GRAVEYARD, filterInstant));
this.getSpellAbility().addTarget(new TargetCard(0, 1, Zone.GRAVEYARD, filterSorcery));
this.getSpellAbility().addEffect(new ExileSourceEffect());
this.getSpellAbility().addEffect(new ExileSpellEffect());
}
private ShredsOfSanity(final ShredsOfSanity card) {

View file

@ -1,25 +1,18 @@
package mage.cards.s;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileCardEnteringGraveyardReplacementEffect;
import mage.abilities.effects.common.ExileSourceEffect;
import mage.cards.Card;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.common.FilterInstantOrSorceryCard;
import mage.game.Game;
import mage.constants.CardType;
import mage.filter.StaticFilters;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect;
/**
* @author emerald000
* @author xenohedron
*/
public final class SinsOfThePast extends CardImpl {
@ -27,9 +20,11 @@ public final class SinsOfThePast extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}{B}");
// Until end of turn, you may cast target instant or sorcery card from your graveyard without paying its mana cost. If that card would be put into your graveyard this turn, exile it instead. Exile Sins of the Past.
this.getSpellAbility().addEffect(new SinsOfThePastEffect());
this.getSpellAbility().addEffect(new ExileSourceEffect());
this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(new FilterInstantOrSorceryCard()));
this.getSpellAbility().addEffect(new MayCastTargetThenExileEffect(true)
.setText("Until end of turn, you may cast target instant or sorcery card from your graveyard without paying its mana cost. "
+ ThatSpellGraveyardExileReplacementEffect.RULE_YOUR));
this.getSpellAbility().addEffect(new ExileSpellEffect());
this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD));
}
private SinsOfThePast(final SinsOfThePast card) {
@ -41,34 +36,3 @@ public final class SinsOfThePast extends CardImpl {
return new SinsOfThePast(this);
}
}
class SinsOfThePastEffect extends OneShotEffect {
SinsOfThePastEffect() {
super(Outcome.PlayForFree);
this.staticText = "Until end of turn, you may cast target instant or sorcery card from your graveyard without paying its mana cost. If that spell would be put into your graveyard, exile it instead";
}
SinsOfThePastEffect(final SinsOfThePastEffect effect) {
super(effect);
}
@Override
public SinsOfThePastEffect copy() {
return new SinsOfThePastEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(this.getTargetPointer().getFirst(game, source));
if (card != null) {
ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect(Zone.GRAVEYARD, TargetController.YOU, Duration.EndOfTurn, true);
effect.setTargetPointer(new FixedTarget(card, game));
game.addEffect(effect, source);
effect = new ExileCardEnteringGraveyardReplacementEffect(card.getId());
game.addEffect(effect, source);
return true;
}
return false;
}
}

View file

@ -1,29 +1,21 @@
package mage.cards.t;
import mage.ApprovingObject;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.keyword.FlashAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterCard;
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.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
* @author fireshoes
* @author xenohedron
*/
public final class TorrentialGearhulk extends CardImpl {
@ -45,7 +37,7 @@ public final class TorrentialGearhulk extends CardImpl {
// When Torrential Gearhulk enters the battlefield, you may cast target
// instant card from your graveyard without paying its mana cost.
// If that card would be put into your graveyard this turn, exile it instead.
Ability ability = new EntersBattlefieldTriggeredAbility(new TorrentialGearhulkEffect());
Ability ability = new EntersBattlefieldTriggeredAbility(new MayCastTargetThenExileEffect(true));
ability.addTarget(new TargetCardInYourGraveyard(filter));
this.addAbility(ability);
}
@ -59,81 +51,3 @@ public final class TorrentialGearhulk extends CardImpl {
return new TorrentialGearhulk(this);
}
}
class TorrentialGearhulkEffect extends OneShotEffect {
TorrentialGearhulkEffect() {
super(Outcome.PlayForFree);
this.staticText = "you may cast target instant card from your graveyard without paying its mana cost. "
+ "If that spell would be put into your graveyard, exile it instead";
}
TorrentialGearhulkEffect(final TorrentialGearhulkEffect effect) {
super(effect);
}
@Override
public TorrentialGearhulkEffect copy() {
return new TorrentialGearhulkEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Card card = game.getCard(this.getTargetPointer().getFirst(game, source));
if (controller != null && card != null) {
if (controller.chooseUse(outcome, "Cast " + card.getLogName() + '?', source, game)) {
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(card, game, true),
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
if (cardWasCast) {
ContinuousEffect effect = new TorrentialGearhulkReplacementEffect(card.getId());
effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId())));
game.addEffect(effect, source);
}
}
return true;
}
return false;
}
}
class TorrentialGearhulkReplacementEffect extends ReplacementEffectImpl {
private final UUID cardId;
TorrentialGearhulkReplacementEffect(UUID cardId) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardId = cardId;
staticText = "If that card would be put into your graveyard this turn, exile it instead";
}
TorrentialGearhulkReplacementEffect(final TorrentialGearhulkReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public TorrentialGearhulkReplacementEffect copy() {
return new TorrentialGearhulkReplacementEffect(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;
return zEvent.getToZone() == Zone.GRAVEYARD
&& zEvent.getTargetId().equals(this.cardId);
}
}

View file

@ -1,36 +1,30 @@
package mage.cards.t;
import java.util.UUID;
import mage.ApprovingObject;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DiesCreatureTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect;
import mage.abilities.keyword.BushidoAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import java.util.UUID;
/**
*
* @author LevelX2
* @author xenohedron
*/
public final class ToshiroUmezawa extends CardImpl {
private static final FilterCard filterInstant = new FilterCard("instant card from your graveyard");
private static final FilterCard filter = new FilterCard("instant card from your graveyard");
static {
filterInstant.add(CardType.INSTANT.getPredicate());
filter.add(CardType.INSTANT.getPredicate());
}
public ToshiroUmezawa(UUID ownerId, CardSetInfo setInfo) {
@ -44,11 +38,12 @@ public final class ToshiroUmezawa extends CardImpl {
// Bushido 1
this.addAbility(new BushidoAbility(1));
// Whenever a creature an opponent controls dies, you may cast target
// instant card from your graveyard. If that card would be put into a
// graveyard this turn, exile it instead.
Ability ability = new DiesCreatureTriggeredAbility(new ToshiroUmezawaEffect(), true, StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE);
ability.addTarget(new TargetCardInYourGraveyard(1, 1, filterInstant));
// Whenever a creature an opponent controls dies, you may cast target instant card from your graveyard. If that card would be put into a graveyard this turn, exile it instead.
Ability ability = new DiesCreatureTriggeredAbility(new MayCastTargetThenExileEffect(false)
.setText("you may cast target instant card from your graveyard. "
+ ThatSpellGraveyardExileReplacementEffect.RULE_A),
true, StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE);
ability.addTarget(new TargetCardInYourGraveyard(filter));
this.addAbility(ability);
}
@ -62,86 +57,3 @@ public final class ToshiroUmezawa extends CardImpl {
return new ToshiroUmezawa(this);
}
}
class ToshiroUmezawaEffect extends OneShotEffect {
public ToshiroUmezawaEffect() {
super(Outcome.Benefit);
this.staticText = "you may cast target instant card from your graveyard. "
+ "If that spell would be put into a graveyard, exile it instead";
}
public ToshiroUmezawaEffect(final ToshiroUmezawaEffect effect) {
super(effect);
}
@Override
public ToshiroUmezawaEffect copy() {
return new ToshiroUmezawaEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (card != null
&& controller.getGraveyard().contains(card.getId())) { // must be in graveyard
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, false),
game, false, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
game.addEffect(new ToshiroUmezawaReplacementEffect(card.getId()), source);
}
}
return false;
}
}
class ToshiroUmezawaReplacementEffect extends ReplacementEffectImpl {
private final UUID cardId;
public ToshiroUmezawaReplacementEffect(UUID cardId) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardId = cardId;
}
public ToshiroUmezawaReplacementEffect(final ToshiroUmezawaReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public ToshiroUmezawaReplacementEffect copy() {
return new ToshiroUmezawaReplacementEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
UUID eventObject = event.getTargetId();
StackObject stackObject = game.getStack().getStackObject(eventObject);
Player controller = game.getPlayer(source.getControllerId());
if (stackObject != null && controller != null) {
if (stackObject instanceof Spell) {
game.rememberLKI(stackObject.getId(), Zone.STACK, stackObject);
}
if (stackObject instanceof Card && eventObject.equals(cardId)) {
return controller.moveCards((Card) stackObject, Zone.EXILED, source, game);
}
}
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;
return zEvent.getToZone() == Zone.GRAVEYARD
&& event.getTargetId().equals(cardId);
}
}

View file

@ -8,15 +8,12 @@ import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.CastCardFromGraveyardThenExileItEffect;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.*;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
@ -42,9 +39,8 @@ public final class VoharVodalianDesecrator extends CardImpl {
this.addAbility(new SimpleActivatedAbility(new VoharVodalianDesecratorEffect(), new TapSourceCost()));
// {2}, Sacrifice Vohar, Vodalian Desecrator: You may cast target instant or sorcery card from your graveyard this turn. If that spell would be put into your graveyard, exile it instead. Activate only as a sorcery.
Ability ability = new ActivateAsSorceryActivatedAbility(new CastCardFromGraveyardThenExileItEffect()
.setText("You may cast target instant or sorcery card from your graveyard this turn. If that spell would be put into your graveyard, exile it instead."),
new GenericManaCost(2)
Ability ability = new ActivateAsSorceryActivatedAbility(
new MayCastTargetThenExileEffect(Duration.EndOfTurn), new GenericManaCost(2)
);
ability.addCost(new SacrificeSourceCost());
ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY_FROM_YOUR_GRAVEYARD));

View file

@ -1,15 +1,11 @@
package mage.cards.w;
import java.util.UUID;
import mage.ApprovingObject;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.MayCastTargetThenExileEffect;
import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect;
import mage.abilities.keyword.IslandwalkAbility;
import mage.abilities.keyword.SwampwalkAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
@ -19,15 +15,14 @@ import mage.filter.predicate.card.OwnerIdPredicate;
import mage.game.Game;
import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInGraveyard;
import java.util.UUID;
/**
*
* @author jeffwadsworth
* @author jeffwadsworth, xenohedron
*/
public final class WrexialTheRisenDeep extends CardImpl {
@ -44,10 +39,8 @@ public final class WrexialTheRisenDeep extends CardImpl {
// Swampwalk
this.addAbility(new SwampwalkAbility());
// Whenever Wrexial, the Risen Deep deals combat damage to a player,
// you may cast target instant or sorcery card from that player's graveyard
// without paying its mana cost. If that card would be put into a graveyard
// this turn, exile it instead.
// Whenever Wrexial, the Risen Deep deals combat damage to a player, you may cast target instant or sorcery card from that player's graveyard without paying its mana cost.
// If that card would be put into a graveyard this turn, exile it instead.
this.addAbility(new WrexialTheRisenDeepTriggeredAbility());
}
@ -63,11 +56,15 @@ public final class WrexialTheRisenDeep extends CardImpl {
class WrexialTheRisenDeepTriggeredAbility extends TriggeredAbilityImpl {
public WrexialTheRisenDeepTriggeredAbility() {
super(Zone.BATTLEFIELD, new WrexialTheRisenDeepEffect(), true);
WrexialTheRisenDeepTriggeredAbility() {
super(Zone.BATTLEFIELD, new MayCastTargetThenExileEffect(true)
.setText("you may cast target instant or sorcery card from "
+ "that player's graveyard without paying its mana cost. "
+ ThatSpellGraveyardExileReplacementEffect.RULE_A), false);
setTriggerPhrase("Whenever {this} deals combat damage to a player, ");
}
public WrexialTheRisenDeepTriggeredAbility(final WrexialTheRisenDeepTriggeredAbility ability) {
private WrexialTheRisenDeepTriggeredAbility(final WrexialTheRisenDeepTriggeredAbility ability) {
super(ability);
}
@ -90,105 +87,15 @@ class WrexialTheRisenDeepTriggeredAbility extends TriggeredAbilityImpl {
if (damagedPlayer == null) {
return false;
}
FilterCard filter = new FilterCard("target instant or sorcery card from "
+ damagedPlayer.getName() + "'s graveyard");
FilterCard filter = new FilterCard("instant or sorcery card from that player's graveyard");
filter.add(new OwnerIdPredicate(damagedPlayer.getId()));
filter.add(Predicates.or(
CardType.INSTANT.getPredicate(),
CardType.SORCERY.getPredicate()));
CardType.SORCERY.getPredicate()
));
Target target = new TargetCardInGraveyard(filter);
this.getTargets().clear();
this.addTarget(target);
return true;
}
@Override
public String getRule() {
return "Whenever {this} deals combat damage to a player, "
+ "you may cast target instant or sorcery card "
+ "from that player's graveyard without paying its mana cost. "
+ "If that spell would be put into a graveyard, exile it instead.";
}
}
class WrexialTheRisenDeepEffect extends OneShotEffect {
public WrexialTheRisenDeepEffect() {
super(Outcome.PlayForFree);
staticText = "you may cast target instant or sorcery card from "
+ "that player's graveyard without paying its mana cost. "
+ "If that spell would be put into a graveyard this turn, exile it instead";
}
public WrexialTheRisenDeepEffect(final WrexialTheRisenDeepEffect effect) {
super(effect);
}
@Override
public WrexialTheRisenDeepEffect copy() {
return new WrexialTheRisenDeepEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Card card = game.getCard(source.getFirstTarget());
if (controller == null
|| card == null) {
return false;
}
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, true),
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
game.addEffect(new WrexialReplacementEffect(card.getId()), source);
return true;
}
}
class WrexialReplacementEffect extends ReplacementEffectImpl {
private final UUID cardid;
public WrexialReplacementEffect(UUID cardid) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardid = cardid;
}
public WrexialReplacementEffect(final WrexialReplacementEffect effect) {
super(effect);
this.cardid = effect.cardid;
}
@Override
public WrexialReplacementEffect copy() {
return new WrexialReplacementEffect(this);
}
@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.getToZone() == Zone.GRAVEYARD
&& event.getTargetId().equals(cardid);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
UUID eventObject = event.getTargetId();
StackObject card = game.getStack().getStackObject(eventObject);
Player controller = game.getPlayer(source.getControllerId());
if (card != null && controller != null) {
if (card instanceof Card) {
return controller.moveCards((Card) card, Zone.EXILED, source, game);
}
}
return false;
}
}

View file

@ -0,0 +1,136 @@
package org.mage.test.cards.replacement;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author xenohedron
*/
public class MayCastTargetThenExileTest extends CardTestPlayerBase {
private static final String spike = "Lava Spike"; // Sorcery {R}; deals 3 damage to target player or planeswalker
@Test
public void testWrexial() {
String wrexial = "Wrexial, the Risen Deep"; // 5/8
/* Whenever Wrexial, the Risen Deep deals combat damage to a player,
* you may cast target instant or sorcery card from that players graveyard without paying its mana cost.
* If that spell would be put into a graveyard, exile it instead.
*/
addCard(Zone.BATTLEFIELD, playerA, wrexial);
addCard(Zone.GRAVEYARD, playerB, spike);
attack(1, playerA, wrexial, playerB);
setChoice(playerA, true);
addTarget(playerA, spike);
addTarget(playerA, playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20 - 5 - 3);
assertExileCount(spike, 1);
}
@Test
public void testForager() {
String forager = "Halo Forager"; // 3/1
/*
* When Halo Forager enters the battlefield, you may pay {X}.
* When you do, you may cast target instant or sorcery card with mana value X from a graveyard without paying its mana cost.
* If that spell would be put into a graveyard, exile it instead.
*/
addCard(Zone.HAND, playerA, forager);
addCard(Zone.GRAVEYARD, playerB, spike);
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 4);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, forager);
setChoice(playerA, true);
setChoice(playerA, "X=1");
addTarget(playerA, spike);
setChoice(playerA, true);
addTarget(playerA, playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20 - 3);
assertExileCount(spike, 1);
}
@Test
public void testVohar() {
String vohar = "Vohar, Vodalian Desecrator"; // 1/2
/*
* {2}, Sacrifice Vohar, Vodalian Desecrator: You may cast target instant or sorcery card from your graveyard this turn.
* If that spell would be put into your graveyard, exile it instead. Activate only as a sorcery.
*/
addCard(Zone.BATTLEFIELD, playerA, vohar);
addCard(Zone.GRAVEYARD, playerA, spike);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, Sacrifice");
addTarget(playerA, spike);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, spike, playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20 - 3);
assertExileCount(spike, 1);
}
@Test
public void testDreadhordeArcanist() {
String arcanist = "Dreadhorde Arcanist"; // 1/3
/*
* Whenever Dreadhorde Arcanist attacks, you may cast target instant or sorcery card with mana value
* less than or equal to Dreadhorde Arcanists power from your graveyard without paying its mana cost.
* If that spell would be put into your graveyard, exile it instead.
*/
addCard(Zone.BATTLEFIELD, playerA, arcanist);
addCard(Zone.GRAVEYARD, playerA, spike);
attack(1, playerA, arcanist, playerB);
setChoice(playerA, true);
addTarget(playerA, spike);
addTarget(playerA, playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20 - 1 - 3);
assertExileCount(spike, 1);
}
@Test
public void testChandra() {
String chandra = "Chandra, Acolyte of Flame"; // 4 loyalty
/*
* 2: You may cast target instant or sorcery card with mana value 3 or less from your graveyard.
* If that spell would be put into your graveyard, exile it instead.
*/
addCard(Zone.BATTLEFIELD, playerA, chandra);
addCard(Zone.GRAVEYARD, playerA, spike);
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2: You may cast");
addTarget(playerA, spike);
setChoice(playerA, true);
addTarget(playerA, playerB);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20 - 3);
assertExileCount(spike, 1);
}
}

View file

@ -1,115 +0,0 @@
package mage.abilities.effects;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
public class CastCardFromGraveyardThenExileItEffect extends OneShotEffect {
public CastCardFromGraveyardThenExileItEffect() {
super(Outcome.Benefit);
}
protected CastCardFromGraveyardThenExileItEffect(final CastCardFromGraveyardThenExileItEffect effect) {
super(effect);
}
@Override
public CastCardFromGraveyardThenExileItEffect copy() {
return new CastCardFromGraveyardThenExileItEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(this.getTargetPointer().getFirst(game, source));
if (card == null) {
return false;
}
ContinuousEffect effect = new CastCardFromGraveyardEffect();
effect.setTargetPointer(new FixedTarget(card, game));
game.addEffect(effect, source);
effect = new ExileReplacementEffect(card.getId());
game.addEffect(effect, source);
return true;
}
}
class CastCardFromGraveyardEffect extends AsThoughEffectImpl {
CastCardFromGraveyardEffect() {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit);
this.staticText = "You may cast target card from your graveyard";
}
private CastCardFromGraveyardEffect(final CastCardFromGraveyardEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public CastCardFromGraveyardEffect copy() {
return new CastCardFromGraveyardEffect(this);
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
return objectId.equals(this.getTargetPointer().getFirst(game, source))
&& affectedControllerId.equals(source.getControllerId());
}
}
class ExileReplacementEffect extends ReplacementEffectImpl {
private final UUID cardId;
ExileReplacementEffect(UUID cardId) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardId = cardId;
this.staticText = "If that card would be put into your graveyard this turn, exile it instead";
}
private ExileReplacementEffect(final ExileReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public ExileReplacementEffect copy() {
return new ExileReplacementEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
Card card = game.getCard(this.cardId);
return controller != null
&& card != null
&& controller.moveCards(card, 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.getToZone() == Zone.GRAVEYARD
&& zEvent.getTargetId().equals(this.cardId);
}
}

View file

@ -1,50 +0,0 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import java.util.UUID;
public class ExileCardEnteringGraveyardReplacementEffect extends ReplacementEffectImpl {
private final UUID cardId;
public ExileCardEnteringGraveyardReplacementEffect(UUID cardId) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardId = cardId;
}
ExileCardEnteringGraveyardReplacementEffect(final ExileCardEnteringGraveyardReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public ExileCardEnteringGraveyardReplacementEffect copy() {
return new ExileCardEnteringGraveyardReplacementEffect(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;
return zEvent.getToZone() == Zone.GRAVEYARD
&& zEvent.getTargetId().equals(this.cardId);
}
}

View file

@ -0,0 +1,98 @@
package mage.abilities.effects.common;
import mage.ApprovingObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.replacement.ThatSpellGraveyardExileReplacementEffect;
import mage.cards.Card;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/**
* @author xenohedron
*/
public class MayCastTargetThenExileEffect extends OneShotEffect {
private final Duration duration;
private final boolean noMana;
/**
* Allows to cast the target card immediately, either for its cost or for free.
* If resulting spell would be put into graveyard, exiles it instead.
*/
public MayCastTargetThenExileEffect(boolean noMana) {
super(Outcome.Benefit);
this.duration = Duration.OneUse;
this.noMana = noMana;
}
/**
* Makes the target card playable for the specified duration as long as it remains in that zone.
* If resulting spell would be put into graveyard, exiles it instead.
*/
public MayCastTargetThenExileEffect(Duration duration) {
super(Outcome.Benefit);
this.duration = duration;
this.noMana = false;
}
protected MayCastTargetThenExileEffect(final MayCastTargetThenExileEffect effect) {
super(effect);
this.duration = effect.duration;
this.noMana = effect.noMana;
}
@Override
public MayCastTargetThenExileEffect copy() {
return new MayCastTargetThenExileEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (card == null) {
return false;
}
FixedTarget fixedTarget = new FixedTarget(card, game);
if (duration == Duration.OneUse) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null || !controller.chooseUse(outcome, "Cast " + card.getLogName() + '?', source, game)) {
return false;
}
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, noMana),
game, noMana, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
} else {
CardUtil.makeCardPlayable(game, source, card, duration, false);
}
ContinuousEffect effect = new ThatSpellGraveyardExileReplacementEffect(true);
effect.setTargetPointer(fixedTarget);
game.addEffect(effect, source);
return true;
}
@Override
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
String text = "you may cast " + getTargetPointer().describeTargets(mode.getTargets(), "it");
if (duration == Duration.EndOfTurn) {
text += " this turn";
} else if (!duration.toString().isEmpty()) {
text += duration.toString();
}
if (noMana) {
text += " without paying its mana cost";
}
return text + ". " + ThatSpellGraveyardExileReplacementEffect.RULE_YOUR;
}
}

View file

@ -0,0 +1,58 @@
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.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.target.targetpointer.FixedTarget;
/**
* @author xenohedron
*/
public class ThatSpellGraveyardExileReplacementEffect extends ReplacementEffectImpl {
public static final String RULE_A = "If that spell would be put into a graveyard, exile it instead.";
public static final String RULE_YOUR = "If that spell would be put into your graveyard, exile it instead.";
/**
* If that spell would be put into a graveyard, exiles it instead.
* Must set target pointer to fixed target.
*/
public ThatSpellGraveyardExileReplacementEffect(boolean yourGraveyard) {
super(Duration.EndOfTurn, Outcome.Exile);
staticText = yourGraveyard ? RULE_YOUR : RULE_A;
}
protected ThatSpellGraveyardExileReplacementEffect(final ThatSpellGraveyardExileReplacementEffect effect) {
super(effect);
}
@Override
public ThatSpellGraveyardExileReplacementEffect copy() {
return new ThatSpellGraveyardExileReplacementEffect(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;
return zEvent.getToZone() == Zone.GRAVEYARD
&& zEvent.getTargetId().equals(((FixedTarget) getTargetPointer()).getTarget())
&& ((FixedTarget) getTargetPointer()).getZoneChangeCounter() + 1
== game.getState().getZoneChangeCounter(zEvent.getTargetId());
}
}