diff --git a/Mage.Sets/src/mage/cards/c/ContainmentPriest.java b/Mage.Sets/src/mage/cards/c/ContainmentPriest.java index 01543f5fa1e..4db1bc165c2 100644 --- a/Mage.Sets/src/mage/cards/c/ContainmentPriest.java +++ b/Mage.Sets/src/mage/cards/c/ContainmentPriest.java @@ -14,7 +14,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.players.Player; -import mage.watchers.common.CreatureWasCastWatcher; +import mage.watchers.common.PermanentWasCastWatcher; import java.util.UUID; @@ -34,7 +34,8 @@ public final class ContainmentPriest extends CardImpl { // Flash this.addAbility(FlashAbility.getInstance()); // If a nontoken creature would enter the battlefield and it wasn't cast, exile it instead. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ContainmentPriestReplacementEffect()), new CreatureWasCastWatcher()); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ContainmentPriestReplacementEffect()), + new PermanentWasCastWatcher()); } private ContainmentPriest(final ContainmentPriest card) { @@ -96,8 +97,8 @@ class ContainmentPriestReplacementEffect extends ReplacementEffectImpl { card = card.getSecondCardFace(); } if (card != null && card.isCreature(game)) { // TODO: Bestow Card cast as Enchantment probably not handled correctly - CreatureWasCastWatcher watcher = game.getState().getWatcher(CreatureWasCastWatcher.class); - return watcher != null && !watcher.wasCreatureCastThisTurn(event.getTargetId()); + PermanentWasCastWatcher watcher = game.getState().getWatcher(PermanentWasCastWatcher.class); + return watcher != null && !watcher.wasPermanentCastThisTurn(event.getTargetId()); } } return false; diff --git a/Mage.Sets/src/mage/cards/h/HallowedMoonlight.java b/Mage.Sets/src/mage/cards/h/HallowedMoonlight.java index 7b44377db2c..ad43eb282ba 100644 --- a/Mage.Sets/src/mage/cards/h/HallowedMoonlight.java +++ b/Mage.Sets/src/mage/cards/h/HallowedMoonlight.java @@ -16,7 +16,7 @@ import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; import mage.players.Player; -import mage.watchers.common.CreatureWasCastWatcher; +import mage.watchers.common.PermanentWasCastWatcher; /** * @@ -29,7 +29,7 @@ public final class HallowedMoonlight extends CardImpl { // Until end of turn, if a creature would enter the battlefield and it wasn't cast, exile it instead. this.getSpellAbility().addEffect(new HallowedMoonlightEffect()); - this.getSpellAbility().addWatcher(new CreatureWasCastWatcher()); + this.getSpellAbility().addWatcher(new PermanentWasCastWatcher()); // Draw a card. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); } @@ -83,8 +83,8 @@ class HallowedMoonlightEffect extends ReplacementEffectImpl { public boolean applies(GameEvent event, Ability source, Game game) { EntersTheBattlefieldEvent entersTheBattlefieldEvent = (EntersTheBattlefieldEvent) event; if (entersTheBattlefieldEvent.getTarget().isCreature(game)) { - CreatureWasCastWatcher watcher = game.getState().getWatcher(CreatureWasCastWatcher.class); - if (watcher != null && !watcher.wasCreatureCastThisTurn(event.getTargetId())) { + PermanentWasCastWatcher watcher = game.getState().getWatcher(PermanentWasCastWatcher.class); + if (watcher != null && !watcher.wasPermanentCastThisTurn(event.getTargetId())) { return true; } } diff --git a/Mage.Sets/src/mage/cards/m/Mistcaller.java b/Mage.Sets/src/mage/cards/m/Mistcaller.java index e67ea0a599b..4d25fe02596 100644 --- a/Mage.Sets/src/mage/cards/m/Mistcaller.java +++ b/Mage.Sets/src/mage/cards/m/Mistcaller.java @@ -14,7 +14,7 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.players.Player; -import mage.watchers.common.CreatureWasCastWatcher; +import mage.watchers.common.PermanentWasCastWatcher; import java.util.UUID; @@ -35,7 +35,7 @@ public final class Mistcaller extends CardImpl { this.addAbility(new SimpleActivatedAbility( new ContainmentPriestReplacementEffect(), new SacrificeSourceCost() - ), new CreatureWasCastWatcher()); + ), new PermanentWasCastWatcher()); } private Mistcaller(final Mistcaller card) { @@ -98,8 +98,8 @@ class ContainmentPriestReplacementEffect extends ReplacementEffectImpl { card = card.getSecondCardFace(); } if (card != null && card.isCreature(game)) { // TODO: Bestow Card cast as Enchantment probably not handled correctly - CreatureWasCastWatcher watcher = game.getState().getWatcher(CreatureWasCastWatcher.class); - return watcher != null && !watcher.wasCreatureCastThisTurn(event.getTargetId()); + PermanentWasCastWatcher watcher = game.getState().getWatcher(PermanentWasCastWatcher.class); + return watcher != null && !watcher.wasPermanentCastThisTurn(event.getTargetId()); } } } diff --git a/Mage.Sets/src/mage/cards/p/PrestonTheVanisher.java b/Mage.Sets/src/mage/cards/p/PrestonTheVanisher.java new file mode 100644 index 00000000000..991a6cde6b9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PrestonTheVanisher.java @@ -0,0 +1,82 @@ + +package mage.cards.p; + +import java.util.UUID; +import mage.MageInt; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldCastTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SetTargetPointer; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.target.common.TargetControlledPermanent; +import mage.target.common.TargetNonlandPermanent; + +/** + * + * @author alexander-novo + */ +public final class PrestonTheVanisher extends CardImpl { + + private static final FilterControlledCreaturePermanent triggerFilter = new FilterControlledCreaturePermanent( + "another nontoken creature"); + private static final FilterControlledPermanent activeCostFilter = new FilterControlledCreaturePermanent( + SubType.ILLUSION, "Illusions"); + + static { + triggerFilter.add(TokenPredicate.FALSE); + triggerFilter.add(AnotherPredicate.instance); + } + + public PrestonTheVanisher(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[] { CardType.CREATURE }, "{3}{W}"); + addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.RABBIT); + this.subtype.add(SubType.WIZARD); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Whenever another nontoken creature enters the battlefield under your control, + // if it wasn’t cast, create a token that’s a copy of that creature, except it’s + // a 0/1 white Illusion. + CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect( + null, null, false, 1, false, false, + null, 0, 1, false); + effect.setOnlyColor(ObjectColor.WHITE); + effect.setOnlySubType(SubType.ILLUSION); + effect.setText("create a token that's a copy of that creature, except it's a 0/1 white Illusion"); + this.addAbility( + new EntersBattlefieldCastTriggeredAbility(Zone.BATTLEFIELD, effect, triggerFilter, false, false, + SetTargetPointer.PERMANENT, null, + true)); + + // {1}{W}, Sacrifice five Illusions: Exile target nonland permanent. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ExileTargetEffect(), + new ManaCostsImpl<>("{1}{W}")); + ability.addCost(new SacrificeTargetCost(new TargetControlledPermanent(5, activeCostFilter))); + ability.addTarget(new TargetNonlandPermanent()); + this.addAbility(ability); + } + + private PrestonTheVanisher(final PrestonTheVanisher card) { + super(card); + } + + @Override + public PrestonTheVanisher copy() { + return new PrestonTheVanisher(this); + } +} diff --git a/Mage.Sets/src/mage/cards/u/UphillBattle.java b/Mage.Sets/src/mage/cards/u/UphillBattle.java index 4cf557658a5..99fe45a4b2d 100644 --- a/Mage.Sets/src/mage/cards/u/UphillBattle.java +++ b/Mage.Sets/src/mage/cards/u/UphillBattle.java @@ -16,7 +16,7 @@ import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.watchers.Watcher; -import mage.watchers.common.CreatureWasCastWatcher; +import mage.watchers.common.PermanentWasCastWatcher; /** * @@ -29,7 +29,7 @@ public final class UphillBattle extends CardImpl { // Creatures played by your opponents enter the battlefield tapped. Ability tapAbility = new SimpleStaticAbility(Zone.BATTLEFIELD, new UphillBattleTapEffect()); - tapAbility.addWatcher(new CreatureWasCastWatcher()); + tapAbility.addWatcher(new PermanentWasCastWatcher()); tapAbility.addWatcher(new PlayCreatureLandWatcher()); addAbility(tapAbility); } @@ -97,11 +97,11 @@ class UphillBattleTapEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { Permanent target = ((EntersTheBattlefieldEvent) event).getTarget(); - CreatureWasCastWatcher creatureSpellWatcher = game.getState().getWatcher(CreatureWasCastWatcher.class); + PermanentWasCastWatcher creatureSpellWatcher = game.getState().getWatcher(PermanentWasCastWatcher.class); PlayCreatureLandWatcher landWatcher = game.getState().getWatcher(PlayCreatureLandWatcher.class); if (target != null - && ((creatureSpellWatcher != null && creatureSpellWatcher.wasCreatureCastThisTurn(target.getId())) + && ((creatureSpellWatcher != null && creatureSpellWatcher.wasPermanentCastThisTurn(target.getId())) || (landWatcher != null && landWatcher.wasLandPlayed(target.getId())))) { target.setTapped(true); } diff --git a/Mage.Sets/src/mage/sets/Jumpstart2022.java b/Mage.Sets/src/mage/sets/Jumpstart2022.java index dce63ca5fcc..a8232718a24 100644 --- a/Mage.Sets/src/mage/sets/Jumpstart2022.java +++ b/Mage.Sets/src/mage/sets/Jumpstart2022.java @@ -557,6 +557,7 @@ public final class Jumpstart2022 extends ExpansionSet { cards.add(new SetCardInfo("Preordain", 63, Rarity.COMMON, mage.cards.p.Preordain.class)); cards.add(new SetCardInfo("Presence of Gond", 709, Rarity.COMMON, mage.cards.p.PresenceOfGond.class)); cards.add(new SetCardInfo("Press for Answers", 337, Rarity.COMMON, mage.cards.p.PressForAnswers.class)); + cards.add(new SetCardInfo("Preston, the Vanisher", 8, Rarity.RARE, mage.cards.p.PrestonTheVanisher.class)); cards.add(new SetCardInfo("Prey Upon", 710, Rarity.COMMON, mage.cards.p.PreyUpon.class)); cards.add(new SetCardInfo("Prickly Marmoset", 580, Rarity.COMMON, mage.cards.p.PricklyMarmoset.class)); cards.add(new SetCardInfo("Pridemalkin", 711, Rarity.COMMON, mage.cards.p.Pridemalkin.class)); diff --git a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldAllTriggeredAbility.java index af1f68a31d8..157333a3542 100644 --- a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldAllTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldAllTriggeredAbility.java @@ -109,7 +109,7 @@ public class EntersBattlefieldAllTriggeredAbility extends TriggeredAbilityImpl { return super.getRule(); } - private String generateTriggerPhrase() { + protected String generateTriggerPhrase() { StringBuilder sb = new StringBuilder("Whenever "); if (thisOrAnother) { sb.append("{this} or another "); diff --git a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldCastTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldCastTriggeredAbility.java new file mode 100644 index 00000000000..508e9532ee6 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldCastTriggeredAbility.java @@ -0,0 +1,103 @@ +package mage.abilities.common; + +import java.util.UUID; + +import mage.abilities.effects.Effect; +import mage.constants.SetTargetPointer; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.watchers.common.PermanentWasCastWatcher; + +/** + * An extension of triggered abilities that trigger when permanents enter the + * battlefield, but this time they either must be cast or must not be cast. + * + * @author alexander-novo + */ +public class EntersBattlefieldCastTriggeredAbility extends EntersBattlefieldAllTriggeredAbility { + private final boolean mustCast; + + /** + * zone = BATTLEFIELD optional = false + * + * @param effect + * @param filter + */ + public EntersBattlefieldCastTriggeredAbility(Effect effect, FilterPermanent filter, boolean mustCast) { + this(Zone.BATTLEFIELD, effect, filter, mustCast, false); + } + + public EntersBattlefieldCastTriggeredAbility(Effect effect, FilterPermanent filter, boolean mustCast, String rule) { + this(Zone.BATTLEFIELD, effect, filter, mustCast, false, rule); + } + + public EntersBattlefieldCastTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean mustCast, + boolean optional) { + this(zone, effect, filter, mustCast, optional, SetTargetPointer.NONE, null, false); + } + + public EntersBattlefieldCastTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean mustCast, + boolean optional, + String rule) { + this(zone, effect, filter, mustCast, optional, rule, false); + } + + public EntersBattlefieldCastTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean mustCast, + boolean optional, + String rule, boolean controlledText) { + this(zone, effect, filter, mustCast, optional, SetTargetPointer.NONE, rule, controlledText); + } + + public EntersBattlefieldCastTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean mustCast, + boolean optional, + SetTargetPointer setTargetPointer, String rule) { + this(zone, effect, filter, mustCast, optional, setTargetPointer, rule, false); + } + + public EntersBattlefieldCastTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean mustCast, + boolean optional, + SetTargetPointer setTargetPointer, String rule, boolean controlledText) { + this(zone, effect, filter, mustCast, optional, setTargetPointer, rule, controlledText, false); + } + + protected EntersBattlefieldCastTriggeredAbility(Zone zone, Effect effect, FilterPermanent filter, boolean mustCast, + boolean optional, + SetTargetPointer setTargetPointer, String rule, boolean controlledText, boolean thisOrAnother) { + super(zone, effect, filter, optional, setTargetPointer, rule, controlledText, thisOrAnother); + + this.mustCast = mustCast; + this.addWatcher(new PermanentWasCastWatcher()); + + StringBuilder triggerPhrase = new StringBuilder(this.generateTriggerPhrase()); + if (mustCast) { + triggerPhrase.append("if it was cast, "); + } else { + triggerPhrase.append("if it wasn't cast, "); + } + this.setTriggerPhrase(triggerPhrase.toString()); + } + + public EntersBattlefieldCastTriggeredAbility(final EntersBattlefieldCastTriggeredAbility ability) { + super(ability); + + this.mustCast = ability.mustCast; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!super.checkTrigger(event, game)) + return false; + + PermanentWasCastWatcher watcher = game.getState().getWatcher(PermanentWasCastWatcher.class); + UUID targetId = event.getTargetId(); + + return watcher.wasPermanentCastThisTurn(targetId) == this.mustCast; + } + + @Override + public EntersBattlefieldCastTriggeredAbility copy() { + return new EntersBattlefieldCastTriggeredAbility(this); + } +} diff --git a/Mage/src/main/java/mage/watchers/common/CreatureWasCastWatcher.java b/Mage/src/main/java/mage/watchers/common/PermanentWasCastWatcher.java similarity index 66% rename from Mage/src/main/java/mage/watchers/common/CreatureWasCastWatcher.java rename to Mage/src/main/java/mage/watchers/common/PermanentWasCastWatcher.java index 61ffaf2359e..39a54d58806 100644 --- a/Mage/src/main/java/mage/watchers/common/CreatureWasCastWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/PermanentWasCastWatcher.java @@ -17,11 +17,11 @@ import mage.watchers.Watcher; * * @author LevelX2 */ -public class CreatureWasCastWatcher extends Watcher { +public class PermanentWasCastWatcher extends Watcher { - private final Set creaturesCasted = new HashSet<>(); + private final Set permanentsCasted = new HashSet<>(); - public CreatureWasCastWatcher() { + public PermanentWasCastWatcher() { super(WatcherScope.GAME); } @@ -31,27 +31,27 @@ public class CreatureWasCastWatcher extends Watcher { Spell spell = (Spell) game.getObject(event.getTargetId()); if (spell != null) { Card card = game.getCard(spell.getSourceId()); - if (card != null && card.isCreature(game)) { - creaturesCasted.add(card.getId()); + if (card != null && card.isPermanent(game)) { + permanentsCasted.add(card.getId()); } } } if (event.getType() == GameEvent.EventType.ZONE_CHANGE && ((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD) { Card card = game.getCard(event.getTargetId()); - if (card != null && card.isCreature(game)) { - creaturesCasted.remove(card.getId()); + if (card != null && card.isPermanent(game)) { + permanentsCasted.remove(card.getId()); } } } - public boolean wasCreatureCastThisTurn(UUID creatureSourceId) { - return creaturesCasted.contains(creatureSourceId); + public boolean wasPermanentCastThisTurn(UUID permanentSourceId) { + return permanentsCasted.contains(permanentSourceId); } @Override public void reset() { super.reset(); - creaturesCasted.clear(); + permanentsCasted.clear(); } }