Consolidate, cleanup & test a few MayCastTargetCardEffect cards

This commit is contained in:
Susucre 2024-04-06 17:37:32 +02:00
parent 68dfcf4eac
commit b233fcf4d8
9 changed files with 173 additions and 214 deletions

View file

@ -5,12 +5,13 @@ import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.common.ChancellorAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.CastTargetForFreeEffect;
import mage.abilities.effects.common.MayCastTargetCardEffect;
import mage.abilities.effects.common.MillCardsEachPlayerEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.CastManaAdjustment;
import mage.constants.SubType;
import mage.constants.TargetController;
import mage.filter.FilterCard;
@ -50,7 +51,7 @@ public final class ChancellorOfTheSpires extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// When Chancellor of the Spires enters the battlefield, you may cast target instant or sorcery card from an opponent's graveyard without paying its mana cost.
Ability ability = new EntersBattlefieldTriggeredAbility(new CastTargetForFreeEffect(), true);
Ability ability = new EntersBattlefieldTriggeredAbility(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST));
ability.addTarget(new TargetCardInOpponentsGraveyard(filter));
this.addAbility(ability);
}

View file

@ -1,26 +1,20 @@
package mage.cards.g;
import mage.ApprovingObject;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.ChooseABackgroundAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card;
import mage.abilities.effects.common.MayCastTargetCardEffect;
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.common.FilterInstantOrSorcerySpell;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTarget;
import mage.watchers.common.CastFromHandWatcher;
import java.util.UUID;
@ -69,9 +63,13 @@ class GaleWaterdeepProdigyTriggeredAbility extends SpellCastControllerTriggeredA
}
public GaleWaterdeepProdigyTriggeredAbility() {
super(new GaleWaterdeepProdigyEffect(),
super(
new MayCastTargetCardEffect(true)
.setText("you may cast up to one target card of the other type from your graveyard. "
+ "If a spell cast from your graveyard this way would be put into your graveyard, exile it instead."),
new FilterInstantOrSorcerySpell("an instant or sorcery spell from your hand"),
false);
false
);
addWatcher(new CastFromHandWatcher());
}
@ -110,81 +108,4 @@ class GaleWaterdeepProdigyTriggeredAbility extends SpellCastControllerTriggeredA
public GaleWaterdeepProdigyTriggeredAbility copy() {
return new GaleWaterdeepProdigyTriggeredAbility(this);
}
}
class GaleWaterdeepProdigyEffect extends OneShotEffect {
GaleWaterdeepProdigyEffect() {
super(Outcome.PutCardInPlay);
this.staticText = "you may cast up to one of the other type from your graveyard. " +
"If a spell cast from your graveyard this way would be put into your graveyard, exile it instead.";
}
private GaleWaterdeepProdigyEffect(final GaleWaterdeepProdigyEffect effect) {
super(effect);
}
@Override
public GaleWaterdeepProdigyEffect copy() {
return new GaleWaterdeepProdigyEffect(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.Neutral, "Cast " + card.getLogName() + '?', source, game)) {
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);
ContinuousEffect effect = new GaleWaterdeepProdigyReplacementEffect(card.getId());
effect.setTargetPointer(new FixedTarget(card.getId(), game.getState().getZoneChangeCounter(card.getId())));
game.addEffect(effect, source);
}
return true;
}
}
class GaleWaterdeepProdigyReplacementEffect extends ReplacementEffectImpl {
private final UUID cardId;
GaleWaterdeepProdigyReplacementEffect(UUID cardId) {
super(Duration.EndOfTurn, Outcome.Exile);
this.cardId = cardId;
staticText = "If a spell cast from your graveyard this way would be put into your graveyard, exile it instead.";
}
private GaleWaterdeepProdigyReplacementEffect(final GaleWaterdeepProdigyReplacementEffect effect) {
super(effect);
this.cardId = effect.cardId;
}
@Override
public GaleWaterdeepProdigyReplacementEffect copy() {
return new GaleWaterdeepProdigyReplacementEffect(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,18 +1,20 @@
package mage.cards.g;
import java.util.UUID;
import mage.ApprovingObject;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.cards.*;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.TargetController;
import mage.abilities.effects.common.MayCastTargetCardEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.CardsImpl;
import mage.constants.*;
import mage.game.Game;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
* @author North
@ -45,8 +47,9 @@ class GalvanothEffect extends OneShotEffect {
GalvanothEffect() {
super(Outcome.PlayForFree);
staticText = "look at the top card of your library. If it's an instant or "
+ "sorcery card, you may cast it without paying its mana cost";
staticText = "look at the top card of your library. "
+ "You may cast it without paying its mana cost "
+ "if it's an instant or sorcery spell";
}
private GalvanothEffect(final GalvanothEffect effect) {
@ -61,12 +64,9 @@ class GalvanothEffect extends OneShotEffect {
if (card != null) {
controller.lookAtCards(source, null, new CardsImpl(card), game);
if (card.isInstantOrSorcery(game)) {
if (controller.chooseUse(Outcome.PlayForFree, "Cast " + card.getName() + " without paying its mana cost?", 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);
}
new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST)
.setTargetPointer(new FixedTarget(card.getId()))
.apply(game, source);
}
}
return true;

View file

@ -1,23 +1,17 @@
package mage.cards.m;
import java.util.UUID;
import mage.ApprovingObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.abilities.effects.common.MayCastTargetCardEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.constants.CastManaAdjustment;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInOpponentsGraveyard;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class MemoryPlunder extends CardImpl {
@ -34,7 +28,7 @@ public final class MemoryPlunder extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U/B}{U/B}{U/B}{U/B}");
// You may cast target instant or sorcery card from an opponent's graveyard without paying its mana cost.
this.getSpellAbility().addEffect(new MemoryPlunderEffect());
this.getSpellAbility().addEffect(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST));
this.getSpellAbility().addTarget(new TargetCardInOpponentsGraveyard(filter));
}
@ -47,40 +41,4 @@ public final class MemoryPlunder extends CardImpl {
public MemoryPlunder copy() {
return new MemoryPlunder(this);
}
}
class MemoryPlunderEffect extends OneShotEffect {
MemoryPlunderEffect() {
super(Outcome.PlayForFree);
this.staticText = "You may cast target instant or sorcery card from "
+ "an opponent's graveyard without paying its mana cost";
}
private MemoryPlunderEffect(final MemoryPlunderEffect effect) {
super(effect);
}
@Override
public MemoryPlunderEffect copy() {
return new MemoryPlunderEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (card != null) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null
&& game.getState().getZone(card.getId()) == Zone.GRAVEYARD
&& controller.chooseUse(Outcome.PlayForFree, "Cast " + card.getName() + " without paying cost?", 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);
return cardWasCast;
}
}
return false;
}
}
}

View file

@ -3,16 +3,13 @@ package mage.cards.v;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.MutatesSourceTriggeredAbility;
import mage.abilities.effects.common.CastTargetForFreeEffect;
import mage.abilities.effects.common.MayCastTargetCardEffect;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.MutateAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.ComparisonType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.common.FilterNoncreatureCard;
import mage.filter.predicate.mageobject.ManaValuePredicate;
@ -53,7 +50,7 @@ public final class VadrokApexOfThunder extends CardImpl {
this.addAbility(FirstStrikeAbility.getInstance());
// Whenever this creature mutates, you may cast target noncreature card with converted mana cost 3 or less from your graveyard without paying its mana cost.
Ability ability = new MutatesSourceTriggeredAbility(new CastTargetForFreeEffect());
Ability ability = new MutatesSourceTriggeredAbility(new MayCastTargetCardEffect(CastManaAdjustment.WITHOUT_PAYING_MANA_COST));
ability.addTarget(new TargetCardInYourGraveyard(filter));
this.addAbility(ability);
}

View file

@ -6,12 +6,18 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* {@link mage.cards.g.GaleWaterdeepProdigy Gale, Waterdeep Prodigy} {2}{U}
* Legendary Creature Human Wizard
* Whenever you cast an instant or sorcery spell from your hand, you may cast up to one target card of the other type from your graveyard. If a spell cast from your graveyard this way would be put into your graveyard, exile it instead.
* Choose a Background (You can have a Background as a second commander.)
* 1/3
*
* @author Rjayz
*/
public class GaleWaterdeepProdigyTest extends CardTestPlayerBase {
@Test
public void TestGaleWaterDeepProdigy() {
public void test_GaleWaterDeepProdigy() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 10);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10);

View file

@ -0,0 +1,71 @@
package org.mage.test.cards.single.mbs;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class GalvanothTest extends CardTestPlayerBase {
/**
* {@link mage.cards.g.Galvanoth Galvanoth} {3}{R}{R}
* Creature Beast
* At the beginning of your upkeep, you may look at the top card of your library. You may cast it without paying its mana cost if its an instant or sorcery spell.
* 3/3
*/
private static final String galvanoth = "Galvanoth";
@Test
public void test_Divination_Cast() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.LIBRARY, playerA, "Divination");
addCard(Zone.BATTLEFIELD, playerA, galvanoth);
setChoice(playerA, true); // yes to look
setChoice(playerA, true); // yes to cast
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertGraveyardCount(playerA, "Divination", 1);
assertHandCount(playerA, 2);
}
@Test
public void test_Divination_No_Cast() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.LIBRARY, playerA, "Divination");
addCard(Zone.BATTLEFIELD, playerA, galvanoth);
setChoice(playerA, true); // yes to look
setChoice(playerA, false); // no to cast
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertHandCount(playerA, 0);
}
@Test
public void test_Creature_NotCastable() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.LIBRARY, playerA, "Goblin Piker");
addCard(Zone.BATTLEFIELD, playerA, galvanoth);
setChoice(playerA, true); // yes to look
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertPermanentCount(playerA, galvanoth, 1);
}
}

View file

@ -0,0 +1,57 @@
package org.mage.test.cards.single.shm;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Susucr
*/
public class MemoryPlunderTest extends CardTestPlayerBase {
/**
* {@link mage.cards.m.MemoryPlunder Memory Plunder} {U/B}{U/B}{U/B}{U/B}
* Instant
* You may cast target instant or sorcery card from an opponents graveyard without paying its mana cost.
*/
private static final String plunder = "Memory Plunder";
@Test
public void test_Divination_Cast() {
setStrictChooseMode(true);
addCard(Zone.GRAVEYARD, playerB, "Divination");
addCard(Zone.HAND, playerA, plunder);
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
castSpell(1, PhaseStep.UPKEEP, playerA, plunder, "Divination");
setChoice(playerA, true); // yes to cast
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertGraveyardCount(playerA, plunder, 1);
assertGraveyardCount(playerB, "Divination", 1); // back in graveyard
assertHandCount(playerA, 2);
}
@Test
public void test_Divination_No_Cast() {
setStrictChooseMode(true);
addCard(Zone.GRAVEYARD, playerB, "Divination");
addCard(Zone.HAND, playerA, plunder);
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
castSpell(1, PhaseStep.UPKEEP, playerA, plunder, "Divination");
setChoice(playerA, false);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertGraveyardCount(playerA, plunder, 1);
assertGraveyardCount(playerB, "Divination", 1); // not moved
assertHandCount(playerA, 0); // no cast so no draw
}
}

View file

@ -1,52 +0,0 @@
package mage.abilities.effects.common;
import mage.ApprovingObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
/**
* @author BetaSteward_at_googlemail.com
*/
public class CastTargetForFreeEffect extends OneShotEffect {
public CastTargetForFreeEffect() {
super(Outcome.Benefit);
}
protected CastTargetForFreeEffect(final CastTargetForFreeEffect effect) {
super(effect);
}
@Override
public CastTargetForFreeEffect copy() {
return new CastTargetForFreeEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Card target = (Card) game.getObject(source.getFirstTarget());
if (controller != null && target != null) {
game.getState().setValue("PlayFromNotOwnHandZone" + target.getId(), Boolean.TRUE);
boolean cardWasCast = controller.cast(controller.chooseAbilityForCast(target, game, true),
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + target.getId(), null);
return cardWasCast;
}
return false;
}
@Override
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
return "you may cast " + getTargetPointer().describeTargets(mode.getTargets(), "that card")
+ " without paying its mana cost";
}
}