diff --git a/Mage.Sets/src/mage/cards/d/DragonhawkFatesTempest.java b/Mage.Sets/src/mage/cards/d/DragonhawkFatesTempest.java new file mode 100644 index 00000000000..4c4cb511396 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DragonhawkFatesTempest.java @@ -0,0 +1,127 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTargets; + +import java.util.Set; +import java.util.UUID; + +/** + * @author notgreat + */ +public final class DragonhawkFatesTempest extends CardImpl { + + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("creatures you control with power 4 or greater"); + + static { + filter.add(new PowerPredicate(ComparisonType.OR_GREATER, 4)); + } + + public DragonhawkFatesTempest(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.DRAGON); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Dragonhawk enters or attacks, exile the top X cards of your library, where X is the number of creatures you control with power 4 or greater. You may play those cards until your next end step. + // At the beginning of your next end step, Dragonhawk deals 2 damage to each opponent for each of those cards that are still exiled. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new DragonhawkExileEffect( + new PermanentsOnBattlefieldCount(filter, null), Duration.UntilYourNextEndStep) + .withTextOptions("those cards", true))); + } + + private DragonhawkFatesTempest(final DragonhawkFatesTempest card) { + super(card); + } + + @Override + public DragonhawkFatesTempest copy() { + return new DragonhawkFatesTempest(this); + } +} + +// Copied from ExileTopXMayPlayUntilEffect but with addDelayedTriggeredAbility +class DragonhawkExileEffect extends ExileTopXMayPlayUntilEffect { + + public DragonhawkExileEffect(DynamicValue amount, Duration duration) { + super(amount, duration); + staticText += ". At the beginning of your next end step, " + DragonhawkFatesTempestDamageEffect.STATIC_TEXT; + } + + private DragonhawkExileEffect(final DragonhawkExileEffect effect) { + super(effect); + } + + @Override + public DragonhawkExileEffect copy() { + return new DragonhawkExileEffect(this); + } + + protected void effectCards(Game game, Ability source, Set cards) { + game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility( + new DragonhawkFatesTempestDamageEffect(new FixedTargets(cards, game)), TargetController.YOU), source); + } +} + +class DragonhawkFatesTempestDamageEffect extends OneShotEffect { + FixedTargets cards; + public static String STATIC_TEXT = "{this} deals 2 damage to each opponent for each of those cards that are still exiled"; + + DragonhawkFatesTempestDamageEffect(FixedTargets cards) { + super(Outcome.Benefit); + this.staticText = STATIC_TEXT; + this.cards = cards; + } + + private DragonhawkFatesTempestDamageEffect(final DragonhawkFatesTempestDamageEffect effect) { + super(effect); + cards = effect.cards; + } + + @Override + public DragonhawkFatesTempestDamageEffect copy() { + return new DragonhawkFatesTempestDamageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + int count = cards.getTargets(game, source).size(); //Automatically filters out moved cards + if (count < 1) { + return false; + } + for (UUID playerId : game.getOpponents(source.getControllerId())) { + player = game.getPlayer(playerId); + if (playerId == null) { + continue; + } + player.damage(count * 2, source.getSourceId(), source, game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/d/DryadMilitant.java b/Mage.Sets/src/mage/cards/d/DryadMilitant.java index 7b422087820..0391293b5b8 100644 --- a/Mage.Sets/src/mage/cards/d/DryadMilitant.java +++ b/Mage.Sets/src/mage/cards/d/DryadMilitant.java @@ -1,22 +1,16 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ReplacementEffectImpl; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -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.filter.StaticFilters; + +import java.util.UUID; /** * @@ -33,7 +27,7 @@ public final class DryadMilitant extends CardImpl { this.toughness = new MageInt(1); // If an instant or sorcery card would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new DryadMilitantReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY, false))); } private DryadMilitant(final DryadMilitant card) { @@ -45,42 +39,3 @@ public final class DryadMilitant extends CardImpl { return new DryadMilitant(this); } } - -class DryadMilitantReplacementEffect extends ReplacementEffectImpl { - - DryadMilitantReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Exile); - staticText = "If an instant or sorcery card would be put into a graveyard from anywhere, exile it instead"; - } - - private DryadMilitantReplacementEffect(final DryadMilitantReplacementEffect effect) { - super(effect); - } - - @Override - public DryadMilitantReplacementEffect copy() { - return new DryadMilitantReplacementEffect(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) { - if (((ZoneChangeEvent)event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && (card.isSorcery(game) || card.isInstant(game))) { - return true; - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/f/FestivalOfEmbers.java b/Mage.Sets/src/mage/cards/f/FestivalOfEmbers.java new file mode 100644 index 00000000000..81cd11c9a27 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FestivalOfEmbers.java @@ -0,0 +1,97 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.SacrificeSourceEffect; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class FestivalOfEmbers extends CardImpl { + + public FestivalOfEmbers(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{R}"); + + + // During your turn, you may cast instant and sorcery spells from your graveyard by paying 1 life in addition to their other costs. + this.addAbility(new SimpleStaticAbility(new FestivalOfEmbersCastEffect())); + + // If a card or token would be put into your graveyard from anywhere, exile it instead. + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(true, true))); + + // {1}{R}: Sacrifice Festival of Embers. + this.addAbility(new SimpleActivatedAbility(new SacrificeSourceEffect(), new ManaCostsImpl<>("{1}{R}"))); + } + + private FestivalOfEmbers(final FestivalOfEmbers card) { + super(card); + } + + @Override + public FestivalOfEmbers copy() { + return new FestivalOfEmbers(this); + } +} + +//Based on Osteomancer Adept +class FestivalOfEmbersCastEffect extends AsThoughEffectImpl { + + FestivalOfEmbersCastEffect() { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.AIDontUseIt); + staticText = "During your turn, you may cast instant and sorcery spells from your graveyard by paying 1 life in addition to their other costs."; + } + + private FestivalOfEmbersCastEffect(final FestivalOfEmbersCastEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public FestivalOfEmbersCastEffect copy() { + return new FestivalOfEmbersCastEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (!source.isControlledBy(affectedControllerId)) { + return false; + } + Card card = game.getCard(objectId); + Player player = game.getPlayer(affectedControllerId); + if (card == null + || player == null + || !game.getActivePlayerId().equals(affectedControllerId) + || !card.isOwnedBy(affectedControllerId) + || !card.isInstantOrSorcery(game) + || !game.getState().getZone(objectId).match(Zone.GRAVEYARD)) { + return false; + } + Costs newCosts = new CostsImpl<>(); + newCosts.addAll(card.getSpellAbility().getCosts()); + newCosts.add(new PayLifeCost(1)); + player.setCastSourceIdWithAlternateMana( + card.getId(), card.getManaCost(), newCosts + ); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/f/ForbiddenCrypt.java b/Mage.Sets/src/mage/cards/f/ForbiddenCrypt.java index 91e3653e637..9f334a41037 100644 --- a/Mage.Sets/src/mage/cards/f/ForbiddenCrypt.java +++ b/Mage.Sets/src/mage/cards/f/ForbiddenCrypt.java @@ -1,10 +1,10 @@ package mage.cards.f; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -14,12 +14,11 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** * * @author Quercitron @@ -30,9 +29,9 @@ public final class ForbiddenCrypt extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{B}{B}"); // If you would draw a card, return a card from your graveyard to your hand instead. If you can't, you lose the game. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ForbiddenCryptDrawCardReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new ForbiddenCryptDrawCardReplacementEffect())); // If a card would be put into your graveyard from anywhere, exile that card instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ForbiddenCryptPutIntoYourGraveyardReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(true, false))); } private ForbiddenCrypt(final ForbiddenCrypt card) { @@ -96,47 +95,4 @@ class ForbiddenCryptDrawCardReplacementEffect extends ReplacementEffectImpl { return event.getPlayerId().equals(source.getControllerId()); } -} - -class ForbiddenCryptPutIntoYourGraveyardReplacementEffect extends ReplacementEffectImpl { - - ForbiddenCryptPutIntoYourGraveyardReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - this.staticText = "If a card would be put into your graveyard from anywhere, exile that card instead"; - } - - private ForbiddenCryptPutIntoYourGraveyardReplacementEffect(final ForbiddenCryptPutIntoYourGraveyardReplacementEffect effect) { - super(effect); - } - - @Override - public ForbiddenCryptPutIntoYourGraveyardReplacementEffect copy() { - return new ForbiddenCryptPutIntoYourGraveyardReplacementEffect(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) { - if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - Permanent permanent = ((ZoneChangeEvent) event).getTarget(); - if (!(permanent instanceof PermanentToken)) { - return true; - } - } - } - return false; - } - -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/GaeasWill.java b/Mage.Sets/src/mage/cards/g/GaeasWill.java index df94ce51f7c..34cc9981afe 100644 --- a/Mage.Sets/src/mage/cards/g/GaeasWill.java +++ b/Mage.Sets/src/mage/cards/g/GaeasWill.java @@ -1,23 +1,19 @@ package mage.cards.g; -import java.util.UUID; - import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.abilities.keyword.SuspendAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; +import java.util.UUID; + /** * * @author weirddan455 @@ -36,7 +32,7 @@ public final class GaeasWill extends CardImpl { this.getSpellAbility().addEffect(new GaeasWillGraveyardEffect()); // If a card would be put into your graveyard from anywhere this turn, exile that card instead. - this.getSpellAbility().addEffect(new GaeassWillReplacementEffect()); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(Duration.EndOfTurn))); } private GaeasWill(final GaeasWill card) { @@ -79,45 +75,3 @@ class GaeasWillGraveyardEffect extends ContinuousEffectImpl { return false; } } - -class GaeassWillReplacementEffect extends ReplacementEffectImpl { - - GaeassWillReplacementEffect() { - super(Duration.EndOfTurn, Outcome.Detriment); - this.staticText = "
If a card would be put into your graveyard from anywhere this turn, exile that card instead"; - } - - private GaeassWillReplacementEffect(final GaeassWillReplacementEffect effect) { - super(effect); - } - - @Override - public GaeassWillReplacementEffect copy() { - return new GaeassWillReplacementEffect(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) { - if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - Permanent permanent = ((ZoneChangeEvent) event).getTarget(); - if (!(permanent instanceof PermanentToken)) { - return true; - } - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/j/JackdawSavior.java b/Mage.Sets/src/mage/cards/j/JackdawSavior.java new file mode 100644 index 00000000000..f5db76b14ee --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JackdawSavior.java @@ -0,0 +1,91 @@ +package mage.cards.j; + +import mage.MageInt; +import mage.abilities.common.DiesThisOrAnotherTriggeredAbility; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.predicate.mageobject.MageObjectReferencePredicate; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author notgreat + */ +public final class JackdawSavior extends CardImpl { + public JackdawSavior(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.CLERIC); + this.power = new MageInt(3); + this.toughness = new MageInt(1); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Jackdaw Savior or another creature you control with flying dies, return another target creature card with lesser mana value from your graveyard to the battlefield. + this.addAbility(new JackdawSaviorDiesThisOrAnotherTriggeredAbility()); + } + + private JackdawSavior(final JackdawSavior card) { + super(card); + } + + @Override + public JackdawSavior copy() { + return new JackdawSavior(this); + } +} + +class JackdawSaviorDiesThisOrAnotherTriggeredAbility extends DiesThisOrAnotherTriggeredAbility { + private static final FilterControlledCreaturePermanent flyingFilter = new FilterControlledCreaturePermanent("creature you control with flying"); + + static { + flyingFilter.add(new AbilityPredicate(FlyingAbility.class)); + } + + public JackdawSaviorDiesThisOrAnotherTriggeredAbility() { + super(new ReturnFromGraveyardToBattlefieldTargetEffect().setText( + "return another target creature card with lesser mana value from your graveyard to the battlefield"), + false, flyingFilter); + } + + protected JackdawSaviorDiesThisOrAnotherTriggeredAbility(final JackdawSaviorDiesThisOrAnotherTriggeredAbility ability) { + super(ability); + } + + @Override + public JackdawSaviorDiesThisOrAnotherTriggeredAbility copy() { + return new JackdawSaviorDiesThisOrAnotherTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (super.checkTrigger(event, game)) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + FilterCard filter = new FilterCreatureCard(); + filter.add(Predicates.not(new MageObjectReferencePredicate(zEvent.getTargetId(), game))); + filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, zEvent.getTarget().getManaValue())); + filter.setMessage("target creature card other than "+zEvent.getTarget().getLogName()+" with mana value less than "+zEvent.getTarget().getManaValue()); + this.getTargets().clear(); + this.addTarget(new TargetCardInYourGraveyard(filter)); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/m/MagusOfTheWill.java b/Mage.Sets/src/mage/cards/m/MagusOfTheWill.java index 9478297dbdb..7fb505b88c4 100644 --- a/Mage.Sets/src/mage/cards/m/MagusOfTheWill.java +++ b/Mage.Sets/src/mage/cards/m/MagusOfTheWill.java @@ -1,7 +1,6 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -9,24 +8,15 @@ import mage.abilities.costs.common.ExileSourceCost; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.ReplacementEffectImpl; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; +import java.util.UUID; + /** * * @author fireshoes @@ -44,7 +34,7 @@ public final class MagusOfTheWill extends CardImpl { // {2}{B}, {T}, Exile Magus of the Will: Until end of turn, you may play cards from your graveyard. // If a card would be put into your graveyard from anywhere else this turn, exile that card instead. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CanPlayCardsFromGraveyardEffect(), new ManaCostsImpl<>("{2}{B}")); - ability.addEffect(new MagusOfTheWillReplacementEffect()); + ability.addEffect(new GraveyardFromAnywhereExileReplacementEffect(Duration.EndOfTurn)); ability.addCost(new TapSourceCost()); ability.addCost(new ExileSourceCost()); this.addAbility(ability); @@ -91,46 +81,3 @@ class CanPlayCardsFromGraveyardEffect extends ContinuousEffectImpl { } } - -class MagusOfTheWillReplacementEffect extends ReplacementEffectImpl { - - MagusOfTheWillReplacementEffect() { - super(Duration.EndOfTurn, Outcome.Detriment); - this.staticText = "If a card would be put into your graveyard from anywhere else this turn, exile that card instead"; - } - - private MagusOfTheWillReplacementEffect(final MagusOfTheWillReplacementEffect effect) { - super(effect); - } - - @Override - public MagusOfTheWillReplacementEffect copy() { - return new MagusOfTheWillReplacementEffect(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) { - if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - Permanent permanent = ((ZoneChangeEvent) event).getTarget(); - if (!(permanent instanceof PermanentToken)) { - return true; - } - } - } - return false; - } - -} diff --git a/Mage.Sets/src/mage/cards/n/Necrodominance.java b/Mage.Sets/src/mage/cards/n/Necrodominance.java index c0ae8beec5f..3aab2e46b60 100644 --- a/Mage.Sets/src/mage/cards/n/Necrodominance.java +++ b/Mage.Sets/src/mage/cards/n/Necrodominance.java @@ -6,18 +6,13 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.common.PayLifeCost; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.SkipDrawStepEffect; import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; import java.util.UUID; @@ -50,7 +45,7 @@ public final class Necrodominance extends CardImpl { )); // If a card or token would be put into your graveyard from anywhere, exile it instead. - this.addAbility(new SimpleStaticAbility(new NecrodominanceReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(true, true))); } private Necrodominance(final Necrodominance card) { @@ -95,50 +90,4 @@ class NecrodominanceEffect extends OneShotEffect { return true; } -} - -// Inspired by [Rest in Peace] and [Wheel of Sun and Moon] -class NecrodominanceReplacementEffect extends ReplacementEffectImpl { - - NecrodominanceReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Exile); - staticText = "If a card or token would be put into your graveyard from anywhere, exile it instead"; - } - - private NecrodominanceReplacementEffect(final NecrodominanceReplacementEffect effect) { - super(effect); - } - - @Override - public NecrodominanceReplacementEffect copy() { - return new NecrodominanceReplacementEffect(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; - if (zEvent.getToZone() != Zone.GRAVEYARD) { - return false; - } - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - return true; - } - Permanent token = game.getPermanent(event.getTargetId()); - if (token != null && token instanceof PermanentToken && token.isOwnedBy(source.getControllerId())) { - return true; - } - return false; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/r/RestInPeace.java b/Mage.Sets/src/mage/cards/r/RestInPeace.java index 3fedd0f1478..68abaaf04b8 100644 --- a/Mage.Sets/src/mage/cards/r/RestInPeace.java +++ b/Mage.Sets/src/mage/cards/r/RestInPeace.java @@ -1,25 +1,15 @@ package mage.cards.r; -import java.util.UUID; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.ExileGraveyardAllPlayersEffect; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; 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.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.players.Player; + +import java.util.UUID; /** * @author LevelX2 @@ -49,7 +39,7 @@ public final class RestInPeace extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new ExileGraveyardAllPlayersEffect())); // If a card or token would be put into a graveyard from anywhere, exile it instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new RestInPeaceReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(false, true))); } private RestInPeace(final RestInPeace card) { @@ -61,36 +51,3 @@ public final class RestInPeace extends CardImpl { return new RestInPeace(this); } } - -class RestInPeaceReplacementEffect extends ReplacementEffectImpl { - - RestInPeaceReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Exile); - staticText = "If a card or token would be put into a graveyard from anywhere, exile it instead"; - } - - private RestInPeaceReplacementEffect(final RestInPeaceReplacementEffect effect) { - super(effect); - } - - @Override - public RestInPeaceReplacementEffect copy() { - return new RestInPeaceReplacementEffect(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) { - return ((ZoneChangeEvent)event).getToZone() == Zone.GRAVEYARD; - } -} diff --git a/Mage.Sets/src/mage/cards/t/TheInfamousCruelclaw.java b/Mage.Sets/src/mage/cards/t/TheInfamousCruelclaw.java new file mode 100644 index 00000000000..6ce8b714afa --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheInfamousCruelclaw.java @@ -0,0 +1,118 @@ +package mage.cards.t; + +import mage.ApprovingObject; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MenaceAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author notgreat + */ +public final class TheInfamousCruelclaw extends CardImpl { + + public TheInfamousCruelclaw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.WEASEL); + this.subtype.add(SubType.MERCENARY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Menace + this.addAbility(new MenaceAbility(false)); + + // Whenever The Infamous Cruelclaw deals combat damage to a player, exile cards from the top of your library until you exile a nonland card. You may cast that card by discarding a card rather than paying its mana cost. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new InfamousCruelclawEffect())); + } + + private TheInfamousCruelclaw(final TheInfamousCruelclaw card) { + super(card); + } + + @Override + public TheInfamousCruelclaw copy() { + return new TheInfamousCruelclaw(this); + } +} + +//Based on Amped Raptor +class InfamousCruelclawEffect extends OneShotEffect { + + InfamousCruelclawEffect() { + super(Outcome.PlayForFree); + staticText = "exile cards from the top of your library until you exile a nonland card. " + + "You may cast that card by discarding a card rather than paying its mana cost."; + } + + private InfamousCruelclawEffect(final InfamousCruelclawEffect effect) { + super(effect); + } + + @Override + public InfamousCruelclawEffect copy() { + return new InfamousCruelclawEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null || !controller.getLibrary().hasCards()) { + return false; + } + for (Card card : controller.getLibrary().getCards(game)) { + controller.moveCards(card, Zone.EXILED, source, game); + if (!card.isLand(game)) { + List castableComponents = CardUtil.getCastableComponents(card, null, source, controller, game, null, false); + if (castableComponents.isEmpty()) { + break; + } + String partsInfo = castableComponents + .stream() + .map(MageObject::getLogName) + .collect(Collectors.joining(" or ")); + if (!controller.chooseUse(Outcome.PlayForFree, "Cast spell by discarding a card instead of mana (" + partsInfo + ")?", source, game)) { + break; + } + castableComponents.forEach(partCard -> game.getState().setValue("PlayFromNotOwnHandZone" + partCard.getId(), Boolean.TRUE)); + SpellAbility chosenAbility = controller.chooseAbilityForCast(card, game, true); + if (chosenAbility != null) { + Card faceCard = game.getCard(chosenAbility.getSourceId()); + if (faceCard != null) { + // discard instead of mana cost + Costs newCosts = new CostsImpl<>(); + newCosts.add(new DiscardCardCost()); + newCosts.addAll(chosenAbility.getCosts()); + controller.setCastSourceIdWithAlternateMana(faceCard.getId(), null, newCosts); + controller.cast( + chosenAbility, game, true, + new ApprovingObject(source, game) + ); + } + } + castableComponents.forEach(partCard -> game.getState().setValue("PlayFromNotOwnHandZone" + partCard.getId(), null)); + break; + } + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/y/YawgmothsAgenda.java b/Mage.Sets/src/mage/cards/y/YawgmothsAgenda.java index 411c15dfe35..3c06fb78394 100644 --- a/Mage.Sets/src/mage/cards/y/YawgmothsAgenda.java +++ b/Mage.Sets/src/mage/cards/y/YawgmothsAgenda.java @@ -1,29 +1,19 @@ package mage.cards.y; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.continuous.CantCastMoreThanOneSpellEffect; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.TargetController; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; +import java.util.UUID; + /** * * @author LevelX2 @@ -34,11 +24,11 @@ public final class YawgmothsAgenda extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{B}{B}"); // You can't cast more than one spell each turn. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CantCastMoreThanOneSpellEffect(TargetController.YOU))); + this.addAbility(new SimpleStaticAbility(new CantCastMoreThanOneSpellEffect(TargetController.YOU))); // You may play cards from your graveyard. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new YawgmothsAgendaCanPlayCardsFromGraveyardEffect())); + this.addAbility(new SimpleStaticAbility(new YawgmothsAgendaCanPlayCardsFromGraveyardEffect())); // If a card would be put into your graveyard from anywhere, exile it instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new YawgmothsAgendaReplacementEffect())); + this.addAbility(new SimpleStaticAbility(new GraveyardFromAnywhereExileReplacementEffect(true, false))); } private YawgmothsAgenda(final YawgmothsAgenda card) { @@ -80,48 +70,4 @@ class YawgmothsAgendaCanPlayCardsFromGraveyardEffect extends ContinuousEffectImp } return false; } - -} - -class YawgmothsAgendaReplacementEffect extends ReplacementEffectImpl { - - YawgmothsAgendaReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - this.staticText = "If a card would be put into your graveyard from anywhere, exile it instead"; - } - - private YawgmothsAgendaReplacementEffect(final YawgmothsAgendaReplacementEffect effect) { - super(effect); - } - - @Override - public YawgmothsAgendaReplacementEffect copy() { - return new YawgmothsAgendaReplacementEffect(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) { - if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - Permanent permanent = ((ZoneChangeEvent) event).getTarget(); - if (!(permanent instanceof PermanentToken)) { - return true; - } - } - } - return false; - } - } diff --git a/Mage.Sets/src/mage/cards/y/YawgmothsWill.java b/Mage.Sets/src/mage/cards/y/YawgmothsWill.java index 0d8678a1ae5..f6af0a369a1 100644 --- a/Mage.Sets/src/mage/cards/y/YawgmothsWill.java +++ b/Mage.Sets/src/mage/cards/y/YawgmothsWill.java @@ -1,27 +1,17 @@ package mage.cards.y; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffectImpl; -import mage.abilities.effects.ReplacementEffectImpl; -import mage.cards.Card; +import mage.abilities.effects.common.replacement.GraveyardFromAnywhereExileReplacementEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.Zone; +import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.events.ZoneChangeEvent; -import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; import mage.players.Player; +import java.util.UUID; + /** * * @author LevelX2 @@ -35,7 +25,7 @@ public final class YawgmothsWill extends CardImpl { this.getSpellAbility().addEffect(new CanPlayCardsFromGraveyardEffect()); // If a card would be put into your graveyard from anywhere this turn, exile that card instead. - this.getSpellAbility().addEffect(new YawgmothsWillReplacementEffect()); + this.getSpellAbility().addEffect(new GraveyardFromAnywhereExileReplacementEffect(Duration.EndOfTurn).concatBy("
")); } private YawgmothsWill(final YawgmothsWill card) { @@ -79,46 +69,3 @@ class CanPlayCardsFromGraveyardEffect extends ContinuousEffectImpl { } } - -class YawgmothsWillReplacementEffect extends ReplacementEffectImpl { - - YawgmothsWillReplacementEffect() { - super(Duration.EndOfTurn, Outcome.Detriment); - this.staticText = "If a card would be put into your graveyard from anywhere this turn, exile that card instead"; - } - - private YawgmothsWillReplacementEffect(final YawgmothsWillReplacementEffect effect) { - super(effect); - } - - @Override - public YawgmothsWillReplacementEffect copy() { - return new YawgmothsWillReplacementEffect(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) { - if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD) { - Card card = game.getCard(event.getTargetId()); - if (card != null && card.isOwnedBy(source.getControllerId())) { - Permanent permanent = ((ZoneChangeEvent) event).getTarget(); - if (!(permanent instanceof PermanentToken)) { - return true; - } - } - } - return false; - } - -} diff --git a/Mage.Sets/src/mage/sets/Bloomburrow.java b/Mage.Sets/src/mage/sets/Bloomburrow.java index 4ce257ae16d..40212886700 100644 --- a/Mage.Sets/src/mage/sets/Bloomburrow.java +++ b/Mage.Sets/src/mage/sets/Bloomburrow.java @@ -80,6 +80,7 @@ public final class Bloomburrow extends ExpansionSet { cards.add(new SetCardInfo("Diresight", 91, Rarity.COMMON, mage.cards.d.Diresight.class)); cards.add(new SetCardInfo("Dour Port-Mage", 47, Rarity.RARE, mage.cards.d.DourPortMage.class)); cards.add(new SetCardInfo("Downwind Ambusher", 92, Rarity.UNCOMMON, mage.cards.d.DownwindAmbusher.class)); + cards.add(new SetCardInfo("Dragonhawk, Fate's Tempest", 132, Rarity.MYTHIC, mage.cards.d.DragonhawkFatesTempest.class)); cards.add(new SetCardInfo("Dreamdew Entrancer", 211, Rarity.RARE, mage.cards.d.DreamdewEntrancer.class)); cards.add(new SetCardInfo("Driftgloom Coyote", 11, Rarity.UNCOMMON, mage.cards.d.DriftgloomCoyote.class)); cards.add(new SetCardInfo("Druid of the Spade", 170, Rarity.COMMON, mage.cards.d.DruidOfTheSpade.class)); @@ -94,6 +95,7 @@ public final class Bloomburrow extends ExpansionSet { cards.add(new SetCardInfo("Fecund Greenshell", 362, Rarity.RARE, mage.cards.f.FecundGreenshell.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Feed the Cycle", 94, Rarity.UNCOMMON, mage.cards.f.FeedTheCycle.class)); cards.add(new SetCardInfo("Fell", 95, Rarity.UNCOMMON, mage.cards.f.Fell.class)); + cards.add(new SetCardInfo("Festival of Embers", 134, Rarity.RARE, mage.cards.f.FestivalOfEmbers.class)); cards.add(new SetCardInfo("Finch Formation", 50, Rarity.COMMON, mage.cards.f.FinchFormation.class)); cards.add(new SetCardInfo("Finneas, Ace Archer", 212, Rarity.RARE, mage.cards.f.FinneasAceArcher.class)); cards.add(new SetCardInfo("Fireglass Mentor", 213, Rarity.UNCOMMON, mage.cards.f.FireglassMentor.class)); @@ -135,6 +137,7 @@ public final class Bloomburrow extends ExpansionSet { cards.add(new SetCardInfo("Intrepid Rabbit", 17, Rarity.COMMON, mage.cards.i.IntrepidRabbit.class)); cards.add(new SetCardInfo("Iridescent Vinelasher", 99, Rarity.RARE, mage.cards.i.IridescentVinelasher.class)); cards.add(new SetCardInfo("Island", 266, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Jackdaw Savior", 18, Rarity.RARE, mage.cards.j.JackdawSavior.class)); cards.add(new SetCardInfo("Jolly Gerbils", 19, Rarity.UNCOMMON, mage.cards.j.JollyGerbils.class)); cards.add(new SetCardInfo("Junkblade Bruiser", 220, Rarity.COMMON, mage.cards.j.JunkbladeBruiser.class)); cards.add(new SetCardInfo("Kastral, the Windcrested", 221, Rarity.RARE, mage.cards.k.KastralTheWindcrested.class)); @@ -259,6 +262,7 @@ public final class Bloomburrow extends ExpansionSet { cards.add(new SetCardInfo("Teapot Slinger", 157, Rarity.UNCOMMON, mage.cards.t.TeapotSlinger.class)); cards.add(new SetCardInfo("Tempest Angler", 235, Rarity.COMMON, mage.cards.t.TempestAngler.class)); cards.add(new SetCardInfo("Tender Wildguide", 196, Rarity.RARE, mage.cards.t.TenderWildguide.class)); + cards.add(new SetCardInfo("The Infamous Cruelclaw", 219, Rarity.MYTHIC, mage.cards.t.TheInfamousCruelclaw.class)); cards.add(new SetCardInfo("Thieving Otter", 390, Rarity.COMMON, mage.cards.t.ThievingOtter.class)); cards.add(new SetCardInfo("Thistledown Players", 35, Rarity.COMMON, mage.cards.t.ThistledownPlayers.class)); cards.add(new SetCardInfo("Thornplate Intimidator", 117, Rarity.COMMON, mage.cards.t.ThornplateIntimidator.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/JackdawSaviorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/JackdawSaviorTest.java new file mode 100644 index 00000000000..651eceefa2d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/blb/JackdawSaviorTest.java @@ -0,0 +1,90 @@ +package org.mage.test.cards.single.blb; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author notgreat + */ +public class JackdawSaviorTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.j.JackdawSavior Jackdaw Savior} {2}{W} + * Creature — Bird Cleric + * Flying + * Whenever Jackdaw Savior or another creature you control with flying dies, + * return another target creature card with lesser mana value from your graveyard to the battlefield. + *

+ * This card is unusual in that "another" here refers to "other than the creature that just died", + * which xmage does not natively support. + */ + + @Test + public void test_Simultaneous() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Jackdaw Savior"); //MV = 3, flying + addCard(Zone.BATTLEFIELD, playerA, "Air Elemental"); //MV = 5, flying + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); //MV = 1 + + addCard(Zone.HAND, playerA, "Damnation"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Damnation"); + setChoice(playerA, ""); //Order triggers + addTarget(playerA, "Jackdaw Savior"); + addTarget(playerA, "Memnite"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, 2); //Air Elemental+Damnation + assertPermanentCount(playerA, "Jackdaw Savior", 1); + assertPermanentCount(playerA, "Memnite", 1); + } + + @Test + public void test_Clones() { + setStrictChooseMode(true); + + addCard(Zone.BATTLEFIELD, playerA, "Jackdaw Savior"); //MV = 3, flying + addCard(Zone.BATTLEFIELD, playerA, "Air Elemental"); //MV = 5, flying + addCard(Zone.BATTLEFIELD, playerA, "Spidersilk Armor"); //+0/+1 to all + + addCard(Zone.HAND, playerA, "Phantasmal Image", 2); //MV = 2, clone + addCard(Zone.HAND, playerA, "Murder", 2); + + addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 10); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); + setChoice(playerA, "Air Elemental"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder"); + addTarget(playerA, "Air Elemental[only copy]"); + // Can't target itself, no valid targets + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image"); + setChoice(playerA, true); + setChoice(playerA, "Air Elemental"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Murder"); + addTarget(playerA, "Air Elemental[only copy]"); + addTarget(playerA, "Phantasmal Image"); // Target the previous one + setChoice(playerA, false); //Don't copy, stay on battlefield as 0/1 + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Murder", 2); + assertGraveyardCount(playerA, "Phantasmal Image", 1); + assertPermanentCount(playerA, "Phantasmal Image", 1); + assertPermanentCount(playerA, "Jackdaw Savior", 1); + assertPermanentCount(playerA, "Air Elemental", 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java index 056f001c92f..8a19656b323 100644 --- a/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DiesThisOrAnotherTriggeredAbility.java @@ -56,19 +56,12 @@ public class DiesThisOrAnotherTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - if (zEvent.isDiesEvent()) { - if (zEvent.getTarget() != null) { - if (!applyFilterOnSource && zEvent.getTarget().getId().equals(this.getSourceId())) { - // TODO: remove this workaround for Basri's Lieutenant - return true; - } else { - if (filter.match(zEvent.getTarget(), getControllerId(), this, game)) { - return true; - } - } - } + if (!zEvent.isDiesEvent() || zEvent.getTarget() == null) { + return false; } - return false; + // TODO: remove applyFilterOnSource workaround for Basri's Lieutenant + return ((!applyFilterOnSource && zEvent.getTarget().getId().equals(this.getSourceId())) + || filter.match(zEvent.getTarget(), getControllerId(), this, game)); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java index f0850a3d560..23ff219ca5a 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTopXMayPlayUntilEffect.java @@ -32,7 +32,7 @@ public class ExileTopXMayPlayUntilEffect extends OneShotEffect { makeText(amount.toString().equals("1") ? "that card" : "those cards", duration == Duration.EndOfTurn); } - private ExileTopXMayPlayUntilEffect(final ExileTopXMayPlayUntilEffect effect) { + protected ExileTopXMayPlayUntilEffect(final ExileTopXMayPlayUntilEffect effect) { super(effect); this.amount = effect.amount.copy(); this.duration = effect.duration; @@ -60,10 +60,15 @@ public class ExileTopXMayPlayUntilEffect extends OneShotEffect { if (!cards.isEmpty()) { game.addEffect(new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, duration) .setTargetPointer(new FixedTargets(cards, game)), source); + effectCards(game, source, cards); } return true; } + protected void effectCards(Game game, Ability source, Set cards) { + //Do nothing, used for derived classes + } + /** * [Until end of turn, ] you may play [refCardText] [this turn] */ @@ -76,7 +81,11 @@ public class ExileTopXMayPlayUntilEffect extends OneShotEffect { String text = "exile the top "; boolean singular = amount.toString().equals("1"); text += singular ? "card" : CardUtil.numberToText(amount.toString()) + " cards"; - text += " of your library. "; + if (amount.toString().equals("X")) { + text += " of your library, where X is " + amount.getMessage() + ". "; + } else { + text += " of your library. "; + } if (durationRuleAtEnd) { text += "You may play " + refCardText + ' ' + (duration == Duration.EndOfTurn ? "this turn" : duration.toString()); } else { diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/GraveyardFromAnywhereExileReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/GraveyardFromAnywhereExileReplacementEffect.java new file mode 100644 index 00000000000..979c26e60a6 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/GraveyardFromAnywhereExileReplacementEffect.java @@ -0,0 +1,92 @@ +package mage.abilities.effects.common.replacement; + +import mage.abilities.Ability; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.Card; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +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.permanent.Permanent; +import mage.game.permanent.PermanentToken; +import mage.util.CardUtil; + +/** + * @author notgreat + */ +public class GraveyardFromAnywhereExileReplacementEffect extends ReplacementEffectImpl { + + private final FilterCard filter; + private final boolean onlyYou; + private final boolean tokens; + + public GraveyardFromAnywhereExileReplacementEffect(FilterCard filter, boolean onlyYou) { + this(Duration.WhileOnBattlefield, filter, onlyYou, false); + } + + public GraveyardFromAnywhereExileReplacementEffect(boolean onlyYou, boolean tokens) { + this(Duration.WhileOnBattlefield, StaticFilters.FILTER_CARD_A, onlyYou, tokens); + } + + public GraveyardFromAnywhereExileReplacementEffect(Duration duration) { + this(duration, StaticFilters.FILTER_CARD_A, true, false); + } + protected GraveyardFromAnywhereExileReplacementEffect(Duration duration, FilterCard filter, boolean onlyYou, boolean tokens) { + super(duration, Outcome.Exile); + this.filter = filter; + this.onlyYou = onlyYou; + this.tokens = tokens; + this.setText(); + } + + private GraveyardFromAnywhereExileReplacementEffect(final GraveyardFromAnywhereExileReplacementEffect effect) { + super(effect); + this.filter = effect.filter; + this.onlyYou = effect.onlyYou; + this.tokens = effect.tokens; + } + + private void setText() { + this.staticText = "If " + CardUtil.addArticle(filter.getMessage()) + (tokens ? " or token" : "") + + " would be put into " + (onlyYou ? "your" : "a") + " graveyard from anywhere" + + (duration == Duration.EndOfTurn ? " this turn" : "") + ", exile " + + ((filter == StaticFilters.FILTER_CARD_A && !tokens) ? "that card" : "it") + " instead"; + } + + @Override + public GraveyardFromAnywhereExileReplacementEffect copy() { + return new GraveyardFromAnywhereExileReplacementEffect(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; + if (zEvent.getToZone() != Zone.GRAVEYARD) { + return false; + } + Card card = game.getCard(event.getTargetId()); + if (card != null && (!onlyYou || card.isOwnedBy(source.getControllerId())) && (filter == null || filter.match(card, game))) { + return true; + } + Permanent token = game.getPermanent(event.getTargetId()); + if (tokens && (token instanceof PermanentToken && (!onlyYou || token.isOwnedBy(source.getControllerId())))) { + return true; + } + return false; + } +}