diff --git a/Mage.Sets/src/mage/cards/g/GraveUpheaval.java b/Mage.Sets/src/mage/cards/g/GraveUpheaval.java index f75d9f37890..3afdacc9c70 100644 --- a/Mage.Sets/src/mage/cards/g/GraveUpheaval.java +++ b/Mage.Sets/src/mage/cards/g/GraveUpheaval.java @@ -1,26 +1,15 @@ package mage.cards.g; -import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ContinuousEffect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect; import mage.abilities.keyword.BasicLandcyclingAbility; -import mage.abilities.keyword.HasteAbility; -import mage.cards.Card; 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.StaticFilters; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetCardInGraveyard; -import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; /** * @@ -32,7 +21,7 @@ public final class GraveUpheaval extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}{R}"); // Put target creature card from a graveyard onto the battlefield under your control. It gains haste. - this.getSpellAbility().addEffect(new GraveUpheavalEffect()); + this.getSpellAbility().addEffect(new ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect()); this.getSpellAbility().addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE)); // Basic landcycling {2} @@ -49,40 +38,3 @@ public final class GraveUpheaval extends CardImpl { } } -class GraveUpheavalEffect extends OneShotEffect { - - public GraveUpheavalEffect() { - super(Outcome.PutCreatureInPlay); - this.staticText = "Put target creature card from a graveyard onto the battlefield under your control. It gains haste"; - } - - public GraveUpheavalEffect(final GraveUpheavalEffect effect) { - super(effect); - } - - @Override - public GraveUpheavalEffect copy() { - return new GraveUpheavalEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - Card card = game.getCard(source.getFirstTarget()); - if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game); - Permanent permanent = game.getPermanent(card.getId()); - if (permanent != null) { - ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom); - effect.setTargetPointer(new FixedTarget(permanent, game)); - game.addEffect(effect, source); - } - return true; - } - - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/l/LilianaWakerOfTheDead.java b/Mage.Sets/src/mage/cards/l/LilianaWakerOfTheDead.java new file mode 100644 index 00000000000..56a80487022 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LilianaWakerOfTheDead.java @@ -0,0 +1,119 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.LoyaltyAbility; +import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.GetEmblemEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.command.emblems.LilianaWakerOfTheDeadEmblem; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCreaturePermanent; +import mage.target.common.TargetDiscard; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * + * @author htrajan + */ +public final class LilianaWakerOfTheDead extends CardImpl { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD, -1); + private static final DynamicValue xValue_hint = new CardsInControllerGraveyardCount(StaticFilters.FILTER_CARD); + + public LilianaWakerOfTheDead(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{B}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.LILIANA); + this.addAbility(new PlaneswalkerEntersWithLoyaltyCountersAbility(4)); + + // +1: Each player discards a card. Each opponent who can't loses 3 life. + this.addAbility(new LoyaltyAbility(new LilianaWakerOfTheDeadDiscardEffect(), 1)); + + // −3: Target creature gets -X/-X until end of turn, where X is the number of cards in your graveyard. + Ability ability = new LoyaltyAbility(new BoostTargetEffect( + xValue, xValue, Duration.EndOfTurn, true + ).setText("target creature gets -X/-X until end of turn, where X is the number of cards in your graveyard"), -3) + .addHint(new ValueHint("Cards in your graveyard", xValue_hint)); + ability.addTarget(new TargetCreaturePermanent()); + this.addAbility(ability); + + // −7: You get an emblem with "At the beginning of combat on your turn, put target creature card from a graveyard onto the battlefield under your control. It gains haste." + this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new LilianaWakerOfTheDeadEmblem()), -7)); + } + + private LilianaWakerOfTheDead(final LilianaWakerOfTheDead card) { + super(card); + } + + @Override + public LilianaWakerOfTheDead copy() { + return new LilianaWakerOfTheDead(this); + } +} + +class LilianaWakerOfTheDeadDiscardEffect extends OneShotEffect { + + LilianaWakerOfTheDeadDiscardEffect() { + super(Outcome.Discard); + staticText = "Each player discards a card. Each opponent who can't loses 3 life"; + } + + private LilianaWakerOfTheDeadDiscardEffect(LilianaWakerOfTheDeadDiscardEffect effect) { + super(effect); + } + + @Override + public LilianaWakerOfTheDeadDiscardEffect copy() { + return new LilianaWakerOfTheDeadDiscardEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Map cardsToDiscard = new HashMap<>(); + if (controller == null) { + return true; + } + // choose cards to discard + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + int numberOfCardsToDiscard = Math.min(1, player.getHand().size()); + Cards cards = new CardsImpl(); + Target target = new TargetDiscard(numberOfCardsToDiscard, numberOfCardsToDiscard, StaticFilters.FILTER_CARD, playerId); + player.chooseTarget(outcome, target, source, game); + cards.addAll(target.getTargets()); + cardsToDiscard.put(playerId, cards); + } + // discard all choosen cards + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + int amountDiscarded = player.discard(cardsToDiscard.get(playerId), source, game).size(); + if (controller.hasOpponent(playerId, game) && amountDiscarded == 0) { + player.loseLife(3, game, false); + } + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/Necromentia.java b/Mage.Sets/src/mage/cards/n/Necromentia.java new file mode 100644 index 00000000000..985e0fc22e5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/Necromentia.java @@ -0,0 +1,122 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ChooseACardNameEffect; +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.filter.FilterCard; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.game.permanent.token.Token; +import mage.game.permanent.token.ZombieToken; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetOpponent; + +import java.util.UUID; + +/** + * + * @author htrajan + */ +public final class Necromentia extends CardImpl { + + public Necromentia(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{B}{B}"); + + // Choose a card name other than a basic land card name. Search target opponent's graveyard, hand, and library for any number of cards with that name and exile them. That player shuffles their library, then creates a 2/2 black Zombie creature token for each card exiled from their hand this way. + this.getSpellAbility().addEffect( + new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.NOT_BASIC_LAND_NAME) + ); + this.getSpellAbility().addEffect(new NecromentiaEffect()); + this.getSpellAbility().addTarget(new TargetOpponent()); + } + + private Necromentia(final Necromentia card) { + super(card); + } + + @Override + public Necromentia copy() { + return new Necromentia(this); + } +} + +class NecromentiaEffect extends OneShotEffect { + + NecromentiaEffect() { + super(Outcome.Benefit); + staticText = "Search target opponent's graveyard, hand, and library for any number of cards with that name and exile them. That player shuffles their library, then creates a 2/2 black Zombie creature token for each card exiled from their hand this way"; + } + + private NecromentiaEffect(NecromentiaEffect effect) { + super(effect); + } + + @Override + public NecromentiaEffect copy() { + return new NecromentiaEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); + Player controller = game.getPlayer(source.getControllerId()); + if (cardName != null && controller != null) { + FilterCard filter = new FilterCard("card named " + cardName); + filter.add(new NamePredicate(cardName)); + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); + + // cards in Graveyard + int cardsCount = (cardName.isEmpty() ? 0 : targetPlayer.getGraveyard().count(filter, game)); + if (cardsCount > 0) { + filter.setMessage("card named " + cardName + " in the graveyard of " + targetPlayer.getName()); + TargetCard target = new TargetCard(0, cardsCount, Zone.GRAVEYARD, filter); + if (controller.choose(Outcome.Exile, targetPlayer.getGraveyard(), target, game)) { + controller.moveCards(new CardsImpl(target.getTargets()), Zone.EXILED, source, game); + } + } + + // cards in Hand + int numberOfCardsExiledFromHand = 0; + cardsCount = (cardName.isEmpty() ? 0 : targetPlayer.getHand().count(filter, game)); + if (cardsCount > 0) { + filter.setMessage("card named " + cardName + " in the hand of " + targetPlayer.getName()); + TargetCard target = new TargetCard(0, cardsCount, Zone.HAND, filter); + if (controller.choose(Outcome.Exile, targetPlayer.getHand(), target, game)) { + numberOfCardsExiledFromHand = target.getTargets().size(); + controller.moveCards(new CardsImpl(target.getTargets()), Zone.EXILED, source, game); + } + } + + // cards in Library + Cards cardsInLibrary = new CardsImpl(); + cardsInLibrary.addAll(targetPlayer.getLibrary().getCards(game)); + cardsCount = (cardName.isEmpty() ? 0 : cardsInLibrary.count(filter, game)); + if (cardsCount > 0) { + filter.setMessage("card named " + cardName + " in the library of " + targetPlayer.getLogName()); + TargetCardInLibrary targetLib = new TargetCardInLibrary(0, cardsCount, filter); + if (controller.choose(Outcome.Exile, cardsInLibrary, targetLib, game)) { + controller.moveCards(new CardsImpl(targetLib.getTargets()), Zone.EXILED, source, game); + } + } + + targetPlayer.shuffleLibrary(source, game); + + if (numberOfCardsExiledFromHand > 0) { + game.getState().applyEffects(game); + Token zombieToken = new ZombieToken(); + zombieToken.putOntoBattlefield(numberOfCardsExiledFromHand, game, source.getId(), targetPlayer.getId()); + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/CoreSet2021.java b/Mage.Sets/src/mage/sets/CoreSet2021.java index 59484cab566..f9349d29899 100644 --- a/Mage.Sets/src/mage/sets/CoreSet2021.java +++ b/Mage.Sets/src/mage/sets/CoreSet2021.java @@ -59,9 +59,11 @@ public final class CoreSet2021 extends ExpansionSet { cards.add(new SetCardInfo("Liliana's Scorn", 329, Rarity.RARE, mage.cards.l.LilianasScorn.class)); cards.add(new SetCardInfo("Liliana's Scrounger", 330, Rarity.UNCOMMON, mage.cards.l.LilianasScrounger.class)); cards.add(new SetCardInfo("Liliana, Death Mage", 328, Rarity.MYTHIC, mage.cards.l.LilianaDeathMage.class)); + cards.add(new SetCardInfo("Liliana, Waker of the Dead", 108, Rarity.MYTHIC, mage.cards.l.LilianaWakerOfTheDead.class)); cards.add(new SetCardInfo("Llanowar Visionary", 193, Rarity.COMMON, mage.cards.l.LlanowarVisionary.class)); cards.add(new SetCardInfo("Mangara, the Diplomat", 27, Rarity.MYTHIC, mage.cards.m.MangaraTheDiplomat.class)); cards.add(new SetCardInfo("Mystic Skyfish", 326, Rarity.COMMON, mage.cards.m.MysticSkyfish.class)); + cards.add(new SetCardInfo("Necromentia", 116, Rarity.RARE, mage.cards.n.Necromentia.class)); cards.add(new SetCardInfo("Pack Leader", 29, Rarity.RARE, mage.cards.p.PackLeader.class)); cards.add(new SetCardInfo("Peer into the Abyss", 117, Rarity.RARE, mage.cards.p.PeerIntoTheAbyss.class)); cards.add(new SetCardInfo("Predatory Wurm", 338, Rarity.UNCOMMON, mage.cards.p.PredatoryWurm.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect.java new file mode 100644 index 00000000000..fb3075a7fae --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect.java @@ -0,0 +1,53 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +public class ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect extends OneShotEffect { + + public ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "put target creature card from a graveyard onto the battlefield under your control. It gains haste"; + } + + public ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect(final ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect effect) { + super(effect); + } + + @Override + public ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect copy() { + return new ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Card card = game.getCard(source.getFirstTarget()); + if (card != null) { + controller.moveCards(card, Zone.BATTLEFIELD, source, game); + Permanent permanent = game.getPermanent(card.getId()); + if (permanent != null) { + ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + } + return true; + } + + return false; + } +} diff --git a/Mage/src/main/java/mage/game/command/emblems/LilianaWakerOfTheDeadEmblem.java b/Mage/src/main/java/mage/game/command/emblems/LilianaWakerOfTheDeadEmblem.java new file mode 100644 index 00000000000..7ae6e94fe4b --- /dev/null +++ b/Mage/src/main/java/mage/game/command/emblems/LilianaWakerOfTheDeadEmblem.java @@ -0,0 +1,30 @@ +package mage.game.command.emblems; + +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.effects.common.ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterCreatureCard; +import mage.game.command.Emblem; +import mage.target.common.TargetCardInGraveyard; + +public final class LilianaWakerOfTheDeadEmblem extends Emblem { + /** + * Emblem with "At the beginning of combat on your turn, put target creature card from a graveyard onto the battlefield under your control. It gains haste." + */ + + private static final FilterCard filter = new FilterCreatureCard("creature card from a graveyard"); + + public LilianaWakerOfTheDeadEmblem() { + setName("Emblem Liliana"); + Ability ability = new BeginningOfCombatTriggeredAbility( + Zone.COMMAND, + new ReturnCreatureFromGraveyardToBattlefieldAndGainHasteEffect(), + TargetController.YOU, false, false); + ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE)); + this.getAbilities().add(ability); + } +}