mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
[SPM] implement The Death of Gwen Stacy and update ExileGraveyardTargetPlayerEffect (#13955)
* refactor ExileGraveyardAllTargetPlayerEffect to allow multiple targets * [SPM] implement The Death of Gwen Stacy * change ExileGraveyardAllTargetPlayerEffect to do one batch movement
This commit is contained in:
parent
d46ccdd8bc
commit
d2a7991f8e
9 changed files with 209 additions and 99 deletions
|
|
@ -40,7 +40,7 @@ public final class CallousBloodmage extends CardImpl {
|
|||
ability.addMode(mode);
|
||||
|
||||
// • Exile target player's graveyard.
|
||||
mode = new Mode(new ExileGraveyardAllTargetPlayerEffect().setText("exile target player's graveyard"));
|
||||
mode = new Mode(new ExileGraveyardAllTargetPlayerEffect());
|
||||
mode.addTarget(new TargetPlayer());
|
||||
ability.addMode(mode);
|
||||
this.addAbility(ability);
|
||||
|
|
|
|||
|
|
@ -35,13 +35,12 @@ public final class DiscipleOfPerdition extends CardImpl {
|
|||
// When Disciple of Perdition dies, choose one. If you have exactly 13 life, you may choose both.
|
||||
// * You draw a card and you lose 1 life.
|
||||
Ability ability = new DiesSourceTriggeredAbility(new DrawCardSourceControllerEffect(1, true), false);
|
||||
ability.getModes().setChooseText("choose one. If you have exactly 13 life, you may choose both.");
|
||||
ability.getModes().setChooseText("choose one. If you have exactly 13 life, you may choose both instead.");
|
||||
ability.getModes().setMoreCondition(2, new LifeCompareCondition(TargetController.YOU, ComparisonType.EQUAL_TO, 13));
|
||||
ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and"));
|
||||
|
||||
// * Exile target opponent's graveyard. That player loses 1 life.
|
||||
ability.addMode(new Mode(new ExileGraveyardAllTargetPlayerEffect()
|
||||
.setText("Exile target opponent's graveyard"))
|
||||
ability.addMode(new Mode(new ExileGraveyardAllTargetPlayerEffect())
|
||||
.addEffect(new LoseLifeTargetEffect(1).setText("that player loses 1 life"))
|
||||
.addTarget(new TargetOpponent()));
|
||||
this.addAbility(ability);
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ public final class ElspethsNightmare extends CardImpl {
|
|||
// III - Exile target opponent's graveyard.
|
||||
sagaAbility.addChapterEffect(
|
||||
this, SagaChapter.CHAPTER_III, SagaChapter.CHAPTER_III,
|
||||
new ExileGraveyardAllTargetPlayerEffect()
|
||||
.setText("exile target opponent's graveyard"),
|
||||
new ExileGraveyardAllTargetPlayerEffect(),
|
||||
new TargetOpponent()
|
||||
);
|
||||
this.addAbility(sagaAbility);
|
||||
|
|
|
|||
|
|
@ -6,21 +6,15 @@ 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.OneShotEffect;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.ExileGraveyardAllTargetPlayerEffect;
|
||||
import mage.abilities.mana.SimpleManaAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPlayer;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -35,7 +29,7 @@ public final class StonespeakerCrystal extends CardImpl {
|
|||
this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, Mana.ColorlessMana(2), new TapSourceCost()));
|
||||
|
||||
// {2}, {T}, Sacrifice Stonespeaker Crystal: Exile any number of target players' graveyards. Draw a card.
|
||||
Ability ability = new SimpleActivatedAbility(new StonespeakerCrystalEffect(), new GenericManaCost(2));
|
||||
Ability ability = new SimpleActivatedAbility(new ExileGraveyardAllTargetPlayerEffect(), new GenericManaCost(2));
|
||||
ability.addEffect(new DrawCardSourceControllerEffect(1));
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addCost(new SacrificeSourceCost());
|
||||
|
|
@ -52,38 +46,3 @@ public final class StonespeakerCrystal extends CardImpl {
|
|||
return new StonespeakerCrystal(this);
|
||||
}
|
||||
}
|
||||
|
||||
class StonespeakerCrystalEffect extends OneShotEffect {
|
||||
|
||||
StonespeakerCrystalEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "exile any number of target players' graveyards";
|
||||
}
|
||||
|
||||
private StonespeakerCrystalEffect(final StonespeakerCrystalEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StonespeakerCrystalEffect copy() {
|
||||
return new StonespeakerCrystalEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
Cards cards = new CardsImpl();
|
||||
this.getTargetPointer()
|
||||
.getTargets(game, source)
|
||||
.stream()
|
||||
.map(game::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Player::getGraveyard)
|
||||
.forEach(cards::addAll);
|
||||
controller.moveCards(cards, Zone.EXILED, source, game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
116
Mage.Sets/src/mage/cards/t/TheDeathOfGwenStacy.java
Normal file
116
Mage.Sets/src/mage/cards/t/TheDeathOfGwenStacy.java
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package mage.cards.t;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SagaAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.DestroyTargetEffect;
|
||||
import mage.abilities.effects.common.ExileGraveyardAllTargetPlayerEffect;
|
||||
import mage.cards.*;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.players.PlayerList;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.target.common.TargetDiscard;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jmlundeen
|
||||
*/
|
||||
public final class TheDeathOfGwenStacy extends CardImpl {
|
||||
|
||||
public TheDeathOfGwenStacy(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}");
|
||||
|
||||
this.subtype.add(SubType.SAGA);
|
||||
|
||||
// (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)
|
||||
SagaAbility sagaAbility = new SagaAbility(this);
|
||||
|
||||
// I -- Destroy target creature.
|
||||
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_I, new DestroyTargetEffect(), new TargetCreaturePermanent());
|
||||
|
||||
// II -- Each player may discard a card. Each player who doesn't loses 3 life.
|
||||
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, new TheDeathOfGwenStacyEffect());
|
||||
|
||||
// III -- Exile any number of target players' graveyards.
|
||||
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new ExileGraveyardAllTargetPlayerEffect(),
|
||||
new TargetPlayer(0, Integer.MAX_VALUE, false));
|
||||
|
||||
this.addAbility(sagaAbility);
|
||||
}
|
||||
|
||||
private TheDeathOfGwenStacy(final TheDeathOfGwenStacy card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TheDeathOfGwenStacy copy() {
|
||||
return new TheDeathOfGwenStacy(this);
|
||||
}
|
||||
}
|
||||
|
||||
class TheDeathOfGwenStacyEffect extends OneShotEffect {
|
||||
|
||||
TheDeathOfGwenStacyEffect() {
|
||||
super(Outcome.Neutral);
|
||||
this.staticText = "each player may discard a card. Each player who doesn't loses 3 life";
|
||||
}
|
||||
|
||||
private TheDeathOfGwenStacyEffect(final TheDeathOfGwenStacyEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TheDeathOfGwenStacyEffect copy() {
|
||||
return new TheDeathOfGwenStacyEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
MageObject sourceObject = source.getSourceObject(game);
|
||||
if (controller == null || sourceObject == null) {
|
||||
return false;
|
||||
}
|
||||
// Store for each player the cards to discard, that's important because all discard shall happen at the same time
|
||||
Map<UUID, Cards> cardsToDiscard = new HashMap<>();
|
||||
PlayerList playersInRange = game.getState().getPlayersInRange(controller.getId(), game);
|
||||
|
||||
// choose cards to discard
|
||||
for (UUID playerId : playersInRange) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
Target target = new TargetDiscard(0, 1, new FilterCard(), playerId)
|
||||
.withChooseHint("Choose a card to discard or lose 3 life");
|
||||
player.chooseTarget(outcome, target, source, game);
|
||||
Cards cards = new CardsImpl(target.getTargets());
|
||||
cardsToDiscard.put(playerId, cards);
|
||||
}
|
||||
}
|
||||
// discard all chosen cards
|
||||
for (UUID playerId : playersInRange) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
Cards cardsPlayer = cardsToDiscard.get(playerId);
|
||||
if (cardsPlayer != null && !cardsPlayer.isEmpty()) {
|
||||
for (UUID cardId : cardsPlayer) {
|
||||
Card card = game.getCard(cardId);
|
||||
player.discard(card, false, source, game);
|
||||
}
|
||||
} else {
|
||||
player.loseLife(3, game, source, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,20 @@
|
|||
package mage.cards.t;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.dynamicvalue.MultipliedValue;
|
||||
import mage.abilities.dynamicvalue.common.CreaturesYouControlCount;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.DamageTargetEffect;
|
||||
import mage.abilities.effects.common.DestroyTargetEffect;
|
||||
import mage.abilities.effects.common.ExileGraveyardAllTargetPlayerEffect;
|
||||
import mage.abilities.hint.common.CreaturesYouControlHint;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.target.common.TargetEnchantmentPermanent;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -48,7 +40,7 @@ public final class ThrabenCharm extends CardImpl {
|
|||
this.getSpellAbility().addMode(mode);
|
||||
|
||||
// * Exile any number of target players' graveyards.
|
||||
mode = new Mode(new ThrabenCharmEffect());
|
||||
mode = new Mode(new ExileGraveyardAllTargetPlayerEffect());
|
||||
mode.addTarget(new TargetPlayer(0, Integer.MAX_VALUE, false));
|
||||
this.getSpellAbility().addMode(mode);
|
||||
}
|
||||
|
|
@ -62,38 +54,3 @@ public final class ThrabenCharm extends CardImpl {
|
|||
return new ThrabenCharm(this);
|
||||
}
|
||||
}
|
||||
|
||||
class ThrabenCharmEffect extends OneShotEffect {
|
||||
|
||||
ThrabenCharmEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "exile any number of target players' graveyards";
|
||||
}
|
||||
|
||||
private ThrabenCharmEffect(final ThrabenCharmEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThrabenCharmEffect copy() {
|
||||
return new ThrabenCharmEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
Cards cards = new CardsImpl();
|
||||
this.getTargetPointer()
|
||||
.getTargets(game, source)
|
||||
.stream()
|
||||
.map(game::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Player::getGraveyard)
|
||||
.forEach(cards::addAll);
|
||||
controller.moveCards(cards, Zone.EXILED, source, game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,6 +272,8 @@ public final class MarvelsSpiderMan extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Taxi Driver", 97, Rarity.COMMON, mage.cards.t.TaxiDriver.class));
|
||||
cards.add(new SetCardInfo("The Clone Saga", 219, Rarity.RARE, mage.cards.t.TheCloneSaga.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("The Clone Saga", 28, Rarity.RARE, mage.cards.t.TheCloneSaga.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("The Death of Gwen Stacy", 223, Rarity.RARE, mage.cards.t.TheDeathOfGwenStacy.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("The Death of Gwen Stacy", 54, Rarity.RARE, mage.cards.t.TheDeathOfGwenStacy.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("The Spot's Portal", 68, Rarity.UNCOMMON, mage.cards.t.TheSpotsPortal.class));
|
||||
cards.add(new SetCardInfo("The Spot, Living Portal", 153, Rarity.RARE, mage.cards.t.TheSpotLivingPortal.class, NON_FULL_USE_VARIOUS));
|
||||
cards.add(new SetCardInfo("The Spot, Living Portal", 231, Rarity.RARE, mage.cards.t.TheSpotLivingPortal.class, NON_FULL_USE_VARIOUS));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package org.mage.test.cards.single.spm;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.player.TestPlayer;
|
||||
import org.mage.test.serverside.base.CardTestCommander4Players;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jmlundeen
|
||||
*/
|
||||
public class TheDeathOfGwenStacyTest extends CardTestCommander4Players {
|
||||
|
||||
/*
|
||||
The Death of Gwen Stacy
|
||||
{2}{B}
|
||||
Enchantment - Saga
|
||||
(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)
|
||||
I -- Destroy target creature.
|
||||
II -- Each player may discard a card. Each player who doesn't loses 3 life.
|
||||
III -- Exile any number of target players' graveyards.
|
||||
*/
|
||||
private static final String theDeathOfGwenStacy = "The Death of Gwen Stacy";
|
||||
|
||||
@Test
|
||||
public void testTheDeathOfGwenStacy() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, theDeathOfGwenStacy);
|
||||
addCard(Zone.HAND, playerC, "Mountain");
|
||||
addCard(Zone.HAND, playerB, "Mountain");
|
||||
|
||||
addTarget(playerA, "Mountain");
|
||||
addTarget(playerD, TestPlayer.TARGET_SKIP);
|
||||
addTarget(playerC, "Mountain");
|
||||
addTarget(playerB, "Mountain");
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 20);
|
||||
assertLife(playerC, 20);
|
||||
assertLife(playerD, 20 - 3);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
|
|
@ -22,7 +28,6 @@ public class ExileGraveyardAllTargetPlayerEffect extends OneShotEffect {
|
|||
public ExileGraveyardAllTargetPlayerEffect(boolean toUniqueExile) {
|
||||
super(Outcome.Exile);
|
||||
this.toUniqueExile = toUniqueExile;
|
||||
staticText = "exile target player's graveyard";
|
||||
}
|
||||
|
||||
private ExileGraveyardAllTargetPlayerEffect(final ExileGraveyardAllTargetPlayerEffect effect) {
|
||||
|
|
@ -38,14 +43,39 @@ public class ExileGraveyardAllTargetPlayerEffect extends OneShotEffect {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
Player targetPlayer = game.getPlayer(this.getTargetPointer().getFirst(game, source));
|
||||
if (targetPlayer == null || controller == null) {
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
Set<Card> cardsToExile = new HashSet<>();
|
||||
|
||||
for (UUID playerId : this.getTargetPointer().getTargets(game, source)) {
|
||||
Player targetPlayer = game.getPlayer(playerId);
|
||||
if (targetPlayer == null) {
|
||||
continue;
|
||||
}
|
||||
cardsToExile.addAll(targetPlayer.getGraveyard().getCards(game));
|
||||
}
|
||||
return toUniqueExile ?
|
||||
controller.moveCardsToExile(
|
||||
targetPlayer.getGraveyard().getCards(game), source, game, true,
|
||||
cardsToExile, source, game, true,
|
||||
CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source)
|
||||
) : controller.moveCards(targetPlayer.getGraveyard(), Zone.EXILED, source, game);
|
||||
) : controller.moveCards(cardsToExile, Zone.EXILED, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(Mode mode) {
|
||||
if (staticText != null && !staticText.isEmpty()) {
|
||||
return staticText;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder("exile ");
|
||||
sb.append(getTargetPointer().describeTargets(mode.getTargets(), "target player"));
|
||||
if (sb.toString().toLowerCase().endsWith("player") || sb.toString().toLowerCase().endsWith("opponent")) {
|
||||
if (getTargetPointer().isPlural(mode.getTargets())) {
|
||||
sb.append("s' graveyards");
|
||||
} else {
|
||||
sb.append("'s graveyard");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue