diff --git a/Mage.Sets/src/mage/cards/c/CallousBloodmage.java b/Mage.Sets/src/mage/cards/c/CallousBloodmage.java index 9459c1ee304..3a71cc6cd02 100644 --- a/Mage.Sets/src/mage/cards/c/CallousBloodmage.java +++ b/Mage.Sets/src/mage/cards/c/CallousBloodmage.java @@ -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); diff --git a/Mage.Sets/src/mage/cards/d/DiscipleOfPerdition.java b/Mage.Sets/src/mage/cards/d/DiscipleOfPerdition.java index d4bad5377df..0358fb12d3b 100644 --- a/Mage.Sets/src/mage/cards/d/DiscipleOfPerdition.java +++ b/Mage.Sets/src/mage/cards/d/DiscipleOfPerdition.java @@ -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); diff --git a/Mage.Sets/src/mage/cards/e/ElspethsNightmare.java b/Mage.Sets/src/mage/cards/e/ElspethsNightmare.java index 96071aa6af5..484d2dc9c64 100644 --- a/Mage.Sets/src/mage/cards/e/ElspethsNightmare.java +++ b/Mage.Sets/src/mage/cards/e/ElspethsNightmare.java @@ -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); diff --git a/Mage.Sets/src/mage/cards/s/StonespeakerCrystal.java b/Mage.Sets/src/mage/cards/s/StonespeakerCrystal.java index ba1cab38591..c9e124b3dae 100644 --- a/Mage.Sets/src/mage/cards/s/StonespeakerCrystal.java +++ b/Mage.Sets/src/mage/cards/s/StonespeakerCrystal.java @@ -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; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TheDeathOfGwenStacy.java b/Mage.Sets/src/mage/cards/t/TheDeathOfGwenStacy.java new file mode 100644 index 00000000000..4dccbe02158 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheDeathOfGwenStacy.java @@ -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 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; + } +} diff --git a/Mage.Sets/src/mage/cards/t/ThrabenCharm.java b/Mage.Sets/src/mage/cards/t/ThrabenCharm.java index b1b63f894cc..7bb28685eb1 100644 --- a/Mage.Sets/src/mage/cards/t/ThrabenCharm.java +++ b/Mage.Sets/src/mage/cards/t/ThrabenCharm.java @@ -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; - } -} diff --git a/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java b/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java index 08bea1868af..6c4f6df4ef4 100644 --- a/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java +++ b/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java @@ -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)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/TheDeathOfGwenStacyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/TheDeathOfGwenStacyTest.java new file mode 100644 index 00000000000..a2d52b5b82f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/spm/TheDeathOfGwenStacyTest.java @@ -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); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileGraveyardAllTargetPlayerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileGraveyardAllTargetPlayerEffect.java index b96ba40fabc..6ae0335d31b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileGraveyardAllTargetPlayerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileGraveyardAllTargetPlayerEffect.java @@ -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 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(); } }