From 9ead88bacb7137ce305eb03f467bb96233e1ec81 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Tue, 19 Apr 2022 18:40:45 -0400 Subject: [PATCH] [SNC] Implemented Endless Detour --- .../java/mage/player/ai/ComputerPlayer.java | 4 +- Mage.Sets/src/mage/cards/a/AetherGust.java | 48 ++----- .../src/mage/cards/a/AngelOfSerenity.java | 4 +- .../cards/d/DarettiIngeniousIconoclast.java | 4 +- Mage.Sets/src/mage/cards/d/DiverSkaab.java | 40 +----- Mage.Sets/src/mage/cards/e/EndlessDetour.java | 36 +++++ .../src/mage/cards/m/MoonsnarePrototype.java | 38 +----- .../src/mage/cards/p/PowerOfPersuasion.java | 41 +----- .../src/mage/cards/r/RevengeOfTheDrowned.java | 41 +----- Mage.Sets/src/mage/cards/r/RunAshore.java | 39 +----- Mage.Sets/src/mage/cards/r/RunOutOfTown.java | 44 +----- .../src/mage/cards/s/SaviorOfOllenbock.java | 4 +- Mage.Sets/src/mage/cards/s/Subtlety.java | 42 +----- .../src/mage/sets/StreetsOfNewCapenna.java | 1 + .../test/cards/single/m20/AetherGustTest.java | 5 - .../java/org/mage/test/player/TestPlayer.java | 4 +- .../PutOnTopOrBottomLibraryTargetEffect.java | 54 ++++++++ ...rgetCardInGraveyardBattlefieldOrStack.java | 127 ++++++++++++++++++ .../TargetCardInGraveyardOrBattlefield.java | 113 ---------------- 19 files changed, 261 insertions(+), 428 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/e/EndlessDetour.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java create mode 100644 Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java delete mode 100644 Mage/src/main/java/mage/target/common/TargetCardInGraveyardOrBattlefield.java diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index d8cda7dc4f..8393ebf864 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -542,7 +542,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { } // Angel of Serenity trigger - if (target.getOriginalTarget() instanceof TargetCardInGraveyardOrBattlefield) { + if (target.getOriginalTarget() instanceof TargetCardInGraveyardBattlefieldOrStack) { Cards cards = new CardsImpl(possibleTargets); List possibleCards = new ArrayList<>(cards.getCards(game)); for (Card card : possibleCards) { @@ -1043,7 +1043,7 @@ public class ComputerPlayer extends PlayerImpl implements Player { return target.isChosen(); } - if (target.getOriginalTarget() instanceof TargetCardInGraveyardOrBattlefield) { + if (target.getOriginalTarget() instanceof TargetCardInGraveyardBattlefieldOrStack) { List cards = new ArrayList<>(); for (Player player : game.getPlayers().values()) { cards.addAll(player.getGraveyard().getCards(game)); diff --git a/Mage.Sets/src/mage/cards/a/AetherGust.java b/Mage.Sets/src/mage/cards/a/AetherGust.java index dbff727bf7..438586ef45 100644 --- a/Mage.Sets/src/mage/cards/a/AetherGust.java +++ b/Mage.Sets/src/mage/cards/a/AetherGust.java @@ -1,19 +1,15 @@ package mage.cards.a; +import mage.MageObject; import mage.ObjectColor; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.filter.common.FilterSpellOrPermanent; import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetSpellOrPermanent; import java.util.UUID; @@ -25,7 +21,7 @@ public final class AetherGust extends CardImpl { private static final FilterSpellOrPermanent filter = new FilterSpellOrPermanent("spell or permanent that's red or green"); - private static final Predicate predicate = Predicates.or( + private static final Predicate predicate = Predicates.or( new ColorPredicate(ObjectColor.RED), new ColorPredicate(ObjectColor.GREEN) ); @@ -39,8 +35,11 @@ public final class AetherGust extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); // Choose target spell or permanent that's red or green. Its owner puts it on the top or bottom of their library. - this.getSpellAbility().addEffect(new AetherGustEffect()); - this.getSpellAbility().addTarget(new TargetSpellOrPermanent(1, 1, filter, false)); + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect( + "choose target spell or permanent that's red or green. " + + "Its owner puts it on the top or bottom of their library" + )); + this.getSpellAbility().addTarget(new TargetSpellOrPermanent(filter)); } private AetherGust(final AetherGust card) { @@ -52,34 +51,3 @@ public final class AetherGust extends CardImpl { return new AetherGust(this); } } - -class AetherGustEffect extends OneShotEffect { - - AetherGustEffect() { - super(Outcome.Removal); - staticText = "Choose target spell or permanent that's red or green. " + - "Its owner puts it on the top or bottom of their library."; - } - - private AetherGustEffect(final AetherGustEffect effect) { - super(effect); - } - - @Override - public AetherGustEffect copy() { - return new AetherGustEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(game.getOwnerId(source.getFirstTarget())); - if (player == null) { - return false; - } - if (player.chooseUse(Outcome.Detriment, "Put the targeted object on the top or bottom of your library?", - "", "Top", "Bottom", source, game)) { - return new PutOnLibraryTargetEffect(true).apply(game, source); - } - return new PutOnLibraryTargetEffect(false).apply(game, source); - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java b/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java index 57d4f57045..f57e574496 100644 --- a/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java +++ b/Mage.Sets/src/mage/cards/a/AngelOfSerenity.java @@ -13,7 +13,7 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.Zone; import mage.filter.predicate.mageobject.AnotherPredicate; -import mage.target.common.TargetCardInGraveyardOrBattlefield; +import mage.target.common.TargetCardInGraveyardBattlefieldOrStack; import java.util.UUID; import mage.filter.common.FilterCreatureCard; @@ -49,7 +49,7 @@ public final class AngelOfSerenity extends CardImpl { Ability ability = new EntersBattlefieldTriggeredAbility( new ExileTargetForSourceEffect().setText(rule), true ); - ability.addTarget(new TargetCardInGraveyardOrBattlefield( + ability.addTarget(new TargetCardInGraveyardBattlefieldOrStack( 0, 3, filterCreatureCard, filterCreaturePermanent )); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/d/DarettiIngeniousIconoclast.java b/Mage.Sets/src/mage/cards/d/DarettiIngeniousIconoclast.java index fdb27d8017..57677a1817 100644 --- a/Mage.Sets/src/mage/cards/d/DarettiIngeniousIconoclast.java +++ b/Mage.Sets/src/mage/cards/d/DarettiIngeniousIconoclast.java @@ -17,7 +17,7 @@ import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.game.permanent.token.DarettiConstructToken; import mage.target.TargetPermanent; -import mage.target.common.TargetCardInGraveyardOrBattlefield; +import mage.target.common.TargetCardInGraveyardBattlefieldOrStack; import mage.target.common.TargetControlledPermanent; import java.util.UUID; @@ -65,7 +65,7 @@ public final class DarettiIngeniousIconoclast extends CardImpl { .setText("Choose target artifact card in a graveyard or artifact on the battlefield. " + "Create three tokens that are copies of it"), -6 ); - ability.addTarget(new TargetCardInGraveyardOrBattlefield(1, 1, + ability.addTarget(new TargetCardInGraveyardBattlefieldOrStack(1, 1, StaticFilters.FILTER_CARD_ARTIFACT, StaticFilters.FILTER_PERMANENT_ARTIFACT)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/DiverSkaab.java b/Mage.Sets/src/mage/cards/d/DiverSkaab.java index 5a4897b176..9995fe173f 100644 --- a/Mage.Sets/src/mage/cards/d/DiverSkaab.java +++ b/Mage.Sets/src/mage/cards/d/DiverSkaab.java @@ -3,16 +3,12 @@ package mage.cards.d; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.ExploitCreatureTriggeredAbility; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; import mage.abilities.keyword.ExploitAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -33,7 +29,9 @@ public final class DiverSkaab extends CardImpl { this.addAbility(new ExploitAbility()); // When Diver Skaab exploits a creature, target creature's owner puts it on the top or bottom of their library. - Ability ability = new ExploitCreatureTriggeredAbility(new DiverSkaabEffect()); + Ability ability = new ExploitCreatureTriggeredAbility(new PutOnTopOrBottomLibraryTargetEffect( + "target creature's owner puts it on the top or bottom of their library" + )); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } @@ -47,33 +45,3 @@ public final class DiverSkaab extends CardImpl { return new DiverSkaab(this); } } - -class DiverSkaabEffect extends OneShotEffect { - - DiverSkaabEffect() { - super(Outcome.Removal); - staticText = "target creature's owner puts it on the top or bottom of their library"; - } - - private DiverSkaabEffect(final DiverSkaabEffect effect) { - super(effect); - } - - @Override - public DiverSkaabEffect copy() { - return new DiverSkaabEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(game.getOwnerId(source.getFirstTarget())); - if (player == null) { - return false; - } - if (player.chooseUse(Outcome.Detriment, "Put the targeted object on the top or bottom of your library?", - "", "Top", "Bottom", source, game)) { - return new PutOnLibraryTargetEffect(true).apply(game, source); - } - return new PutOnLibraryTargetEffect(false).apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/e/EndlessDetour.java b/Mage.Sets/src/mage/cards/e/EndlessDetour.java new file mode 100644 index 0000000000..1a06a8cc9c --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EndlessDetour.java @@ -0,0 +1,36 @@ +package mage.cards.e; + +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.target.common.TargetCardInGraveyardBattlefieldOrStack; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class EndlessDetour extends CardImpl { + + public EndlessDetour(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{G}{W}{U}"); + + // The owner of target spell, nonland permanent, or card in a graveyard puts it on the top or bottom of their library. + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect()); + this.getSpellAbility().addTarget(new TargetCardInGraveyardBattlefieldOrStack( + 1, 1, StaticFilters.FILTER_CARD, StaticFilters.FILTER_PERMANENT_NON_LAND, + StaticFilters.FILTER_SPELL, "spell, nonland permanent, or card in a graveyard" + )); + } + + private EndlessDetour(final EndlessDetour card) { + super(card); + } + + @Override + public EndlessDetour copy() { + return new EndlessDetour(this); + } +} diff --git a/Mage.Sets/src/mage/cards/m/MoonsnarePrototype.java b/Mage.Sets/src/mage/cards/m/MoonsnarePrototype.java index 741687662a..a13956b109 100644 --- a/Mage.Sets/src/mage/cards/m/MoonsnarePrototype.java +++ b/Mage.Sets/src/mage/cards/m/MoonsnarePrototype.java @@ -2,19 +2,15 @@ package mage.cards.m; import mage.abilities.Ability; import mage.abilities.costs.common.TapTargetCost; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; import mage.abilities.keyword.ChannelAbility; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.permanent.TappedPredicate; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetControlledPermanent; import mage.target.common.TargetNonlandPermanent; @@ -45,7 +41,7 @@ public final class MoonsnarePrototype extends CardImpl { this.addAbility(ability); // Channel — {4}{U}, Discard Moonsnare Prototype: The owner of target nonland permanent puts it on the top or bottom of their library. - ability = new ChannelAbility("{4}{U}", new MoonsnarePrototypeEffect()); + ability = new ChannelAbility("{4}{U}", new PutOnTopOrBottomLibraryTargetEffect()); ability.addTarget(new TargetNonlandPermanent()); this.addAbility(ability); } @@ -59,33 +55,3 @@ public final class MoonsnarePrototype extends CardImpl { return new MoonsnarePrototype(this); } } - -class MoonsnarePrototypeEffect extends OneShotEffect { - - MoonsnarePrototypeEffect() { - super(Outcome.Benefit); - staticText = "the owner of target nonland permanent puts it on the top or bottom of their library"; - } - - private MoonsnarePrototypeEffect(final MoonsnarePrototypeEffect effect) { - super(effect); - } - - @Override - public MoonsnarePrototypeEffect copy() { - return new MoonsnarePrototypeEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(game.getOwnerId(source.getFirstTarget())); - if (player == null) { - return false; - } - if (player.chooseUse(Outcome.Detriment, "Put the targeted object on the top or bottom of your library?", - "", "Top", "Bottom", source, game)) { - return new PutOnLibraryTargetEffect(true).apply(game, source); - } - return new PutOnLibraryTargetEffect(false).apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java b/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java index 3e112cb30f..9f3c975acc 100644 --- a/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java +++ b/Mage.Sets/src/mage/cards/p/PowerOfPersuasion.java @@ -1,8 +1,6 @@ package mage.cards.p; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.abilities.effects.common.RollDieWithResultTableEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; @@ -10,9 +8,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Outcome; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetOpponentsCreaturePermanent; import java.util.UUID; @@ -36,7 +31,9 @@ public final class PowerOfPersuasion extends CardImpl { effect.addTableEntry(1, 9, new ReturnToHandTargetEffect().setText("return it to its owner's hand")); // 10-19 | Its owner puts it on the top of bottom of their library. - effect.addTableEntry(10, 19, new PowerOfPersuasionEffect()); + effect.addTableEntry(10, 19, new PutOnTopOrBottomLibraryTargetEffect( + "its owner puts it on the top of bottom of their library" + )); // 20 | Gain control of it until the end of your next turn. effect.addTableEntry(20, 20, new GainControlTargetEffect( @@ -53,33 +50,3 @@ public final class PowerOfPersuasion extends CardImpl { return new PowerOfPersuasion(this); } } - -class PowerOfPersuasionEffect extends OneShotEffect { - - PowerOfPersuasionEffect() { - super(Outcome.Benefit); - staticText = "its owner puts it on the top or bottom of their library"; - } - - private PowerOfPersuasionEffect(final PowerOfPersuasionEffect effect) { - super(effect); - } - - @Override - public PowerOfPersuasionEffect copy() { - return new PowerOfPersuasionEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(game.getOwnerId(source.getFirstTarget())); - if (player == null) { - return false; - } - if (player.chooseUse(Outcome.Detriment, "Put the targeted object on the top or bottom of your library?", - "", "Top", "Bottom", source, game)) { - return new PutOnLibraryTargetEffect(true).apply(game, source); - } - return new PutOnLibraryTargetEffect(false).apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/r/RevengeOfTheDrowned.java b/Mage.Sets/src/mage/cards/r/RevengeOfTheDrowned.java index 0187a86e45..c33b254f0a 100644 --- a/Mage.Sets/src/mage/cards/r/RevengeOfTheDrowned.java +++ b/Mage.Sets/src/mage/cards/r/RevengeOfTheDrowned.java @@ -1,16 +1,11 @@ package mage.cards.r; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; -import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.game.Game; import mage.game.permanent.token.ZombieDecayedToken; -import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import java.util.UUID; @@ -24,7 +19,9 @@ public final class RevengeOfTheDrowned extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}"); // Target creature's owner puts it on the top or bottom of their library. You create a 2/2 black Zombie creature token with decayed. - this.getSpellAbility().addEffect(new RevengeOfTheDrownedEffect()); + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect( + "target creature's owner puts it on the top or bottom of their library" + )); this.getSpellAbility().addEffect(new CreateTokenEffect(new ZombieDecayedToken()).concatBy("You")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } @@ -38,33 +35,3 @@ public final class RevengeOfTheDrowned extends CardImpl { return new RevengeOfTheDrowned(this); } } - -class RevengeOfTheDrownedEffect extends OneShotEffect { - - RevengeOfTheDrownedEffect() { - super(Outcome.Benefit); - staticText = "target creature's owner puts it on the top or bottom of their library."; - } - - private RevengeOfTheDrownedEffect(final RevengeOfTheDrownedEffect effect) { - super(effect); - } - - @Override - public RevengeOfTheDrownedEffect copy() { - return new RevengeOfTheDrownedEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(game.getOwnerId(source.getFirstTarget())); - if (player == null) { - return false; - } - if (player.chooseUse(Outcome.Detriment, "Put the targeted object on the top or bottom of your library?", - "", "Top", "Bottom", source, game)) { - return new PutOnLibraryTargetEffect(true).apply(game, source); - } - return new PutOnLibraryTargetEffect(false).apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/r/RunAshore.java b/Mage.Sets/src/mage/cards/r/RunAshore.java index 5b761dfcd0..15252e271f 100644 --- a/Mage.Sets/src/mage/cards/r/RunAshore.java +++ b/Mage.Sets/src/mage/cards/r/RunAshore.java @@ -1,16 +1,11 @@ package mage.cards.r; -import mage.abilities.Ability; import mage.abilities.Mode; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetNonlandPermanent; import java.util.UUID; @@ -28,7 +23,7 @@ public final class RunAshore extends CardImpl { this.getSpellAbility().getModes().setMaxModes(2); // • The owner of target nonland permanent puts it on the top or bottom of their library. - this.getSpellAbility().addEffect(new RunAshoreEffect()); + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect()); this.getSpellAbility().addTarget(new TargetNonlandPermanent()); // • Return target nonland permanent to its owner's hand. @@ -46,33 +41,3 @@ public final class RunAshore extends CardImpl { return new RunAshore(this); } } - -class RunAshoreEffect extends OneShotEffect { - - RunAshoreEffect() { - super(Outcome.Benefit); - staticText = "The owner of target nonland permanent puts it on the top or bottom of their library."; - } - - private RunAshoreEffect(final RunAshoreEffect effect) { - super(effect); - } - - @Override - public RunAshoreEffect copy() { - return new RunAshoreEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(game.getOwnerId(source.getFirstTarget())); - if (player == null) { - return false; - } - if (player.chooseUse(Outcome.Detriment, "Put the targeted object on the top or bottom of your library?", - "", "Top", "Bottom", source, game)) { - return new PutOnLibraryTargetEffect(true).apply(game, source); - } - return new PutOnLibraryTargetEffect(false).apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/r/RunOutOfTown.java b/Mage.Sets/src/mage/cards/r/RunOutOfTown.java index de5bf5d691..5f5942a0d0 100644 --- a/Mage.Sets/src/mage/cards/r/RunOutOfTown.java +++ b/Mage.Sets/src/mage/cards/r/RunOutOfTown.java @@ -1,20 +1,14 @@ package mage.cards.r; -import java.util.UUID; - -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetNonlandPermanent; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class RunOutOfTown extends CardImpl { @@ -23,7 +17,7 @@ public final class RunOutOfTown extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}"); // The owner of target nonland permanent puts it on the top or bottom of their library. - this.getSpellAbility().addEffect(new RunOutOfTownEffect()); + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect()); this.getSpellAbility().addTarget(new TargetNonlandPermanent()); } @@ -36,33 +30,3 @@ public final class RunOutOfTown extends CardImpl { return new RunOutOfTown(this); } } - -class RunOutOfTownEffect extends OneShotEffect { - - public RunOutOfTownEffect() { - super(Outcome.Removal); - this.staticText = "The owner of target nonland permanent puts it on the top or bottom of their library"; - } - - private RunOutOfTownEffect(final RunOutOfTownEffect effect) { - super(effect); - } - - @Override - public RunOutOfTownEffect copy() { - return new RunOutOfTownEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(game.getOwnerId(source.getFirstTarget())); - if (player == null) { - return false; - } - if (player.chooseUse(Outcome.Detriment, "Put the targeted object on the top or bottom of your library?", - "", "Top", "Bottom", source, game)) { - return new PutOnLibraryTargetEffect(true).apply(game, source); - } - return new PutOnLibraryTargetEffect(false).apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/s/SaviorOfOllenbock.java b/Mage.Sets/src/mage/cards/s/SaviorOfOllenbock.java index c1e8fdd262..c27f6a6839 100644 --- a/Mage.Sets/src/mage/cards/s/SaviorOfOllenbock.java +++ b/Mage.Sets/src/mage/cards/s/SaviorOfOllenbock.java @@ -18,7 +18,7 @@ import mage.game.ExileZone; import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; -import mage.target.common.TargetCardInGraveyardOrBattlefield; +import mage.target.common.TargetCardInGraveyardBattlefieldOrStack; import mage.util.CardUtil; import java.util.UUID; @@ -60,7 +60,7 @@ class SaviorOfOllenbockTriggeredAbility extends TriggeredAbilityImpl { SaviorOfOllenbockTriggeredAbility() { super(Zone.BATTLEFIELD, new ExileTargetForSourceEffect()); - this.addTarget(new TargetCardInGraveyardOrBattlefield( + this.addTarget(new TargetCardInGraveyardBattlefieldOrStack( 0, 1, StaticFilters.FILTER_CARD_CREATURE, StaticFilters.FILTER_CONTROLLED_ANOTHER_CREATURE diff --git a/Mage.Sets/src/mage/cards/s/Subtlety.java b/Mage.Sets/src/mage/cards/s/Subtlety.java index 85d7e32625..bdb8052ea8 100644 --- a/Mage.Sets/src/mage/cards/s/Subtlety.java +++ b/Mage.Sets/src/mage/cards/s/Subtlety.java @@ -5,22 +5,18 @@ import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.costs.common.ExileFromHandCost; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.PutOnLibraryTargetEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; import mage.abilities.keyword.EvokeAbility; import mage.abilities.keyword.FlashAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; import mage.filter.FilterCard; import mage.filter.FilterSpell; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; -import mage.game.Game; -import mage.players.Player; import mage.target.TargetSpell; import mage.target.common.TargetCardInHand; @@ -57,7 +53,10 @@ public final class Subtlety extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Subtlety enters the battlefield, choose up to one target creature spell or planeswalker spell. Its owner puts it on the top or bottom of their library. - Ability ability = new EntersBattlefieldTriggeredAbility(new SubtletyEffect()); + Ability ability = new EntersBattlefieldTriggeredAbility(new PutOnTopOrBottomLibraryTargetEffect( + "choose up to one target creature spell or planeswalker spell. " + + "Its owner puts it on the top or bottom of their library" + )); ability.addTarget(new TargetSpell(0, 1, filter)); this.addAbility(ability); @@ -74,34 +73,3 @@ public final class Subtlety extends CardImpl { return new Subtlety(this); } } - -class SubtletyEffect extends OneShotEffect { - - SubtletyEffect() { - super(Outcome.Removal); - staticText = "choose up to one target creature spell or planeswalker spell. " + - "Its owner puts it on the top or bottom of their library"; - } - - private SubtletyEffect(final SubtletyEffect effect) { - super(effect); - } - - @Override - public SubtletyEffect copy() { - return new SubtletyEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(game.getOwnerId(source.getFirstTarget())); - if (player == null) { - return false; - } - if (player.chooseUse(Outcome.Detriment, "Put the targeted spell on the top or bottom of your library?", - "", "Top", "Bottom", source, game)) { - return new PutOnLibraryTargetEffect(true).apply(game, source); - } - return new PutOnLibraryTargetEffect(false).apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java b/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java index f25132cd87..a1fd2f39da 100644 --- a/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java +++ b/Mage.Sets/src/mage/sets/StreetsOfNewCapenna.java @@ -97,6 +97,7 @@ public final class StreetsOfNewCapenna extends ExpansionSet { cards.add(new SetCardInfo("Echo Inspector", 40, Rarity.COMMON, mage.cards.e.EchoInspector.class)); cards.add(new SetCardInfo("Elegant Entourage", 143, Rarity.UNCOMMON, mage.cards.e.ElegantEntourage.class)); cards.add(new SetCardInfo("Elspeth Resplendent", 11, Rarity.MYTHIC, mage.cards.e.ElspethResplendent.class)); + cards.add(new SetCardInfo("Endless Detour", 183, Rarity.RARE, mage.cards.e.EndlessDetour.class)); cards.add(new SetCardInfo("Errant, Street Artist", 41, Rarity.RARE, mage.cards.e.ErrantStreetArtist.class)); cards.add(new SetCardInfo("Evolving Door", 144, Rarity.RARE, mage.cards.e.EvolvingDoor.class)); cards.add(new SetCardInfo("Exhibition Magician", 106, Rarity.COMMON, mage.cards.e.ExhibitionMagician.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/m20/AetherGustTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/m20/AetherGustTest.java index 92fbbf2ecf..b34e46b108 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/m20/AetherGustTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/m20/AetherGustTest.java @@ -1,5 +1,3 @@ - - package org.mage.test.cards.single.m20; import mage.constants.PhaseStep; @@ -8,10 +6,8 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * * @author jgray1206 */ - public class AetherGustTest extends CardTestPlayerBase { /* Aether Gust - Instant {1}{U} @@ -70,5 +66,4 @@ public class AetherGustTest extends CardTestPlayerBase { assertLibraryCount(playerA, 1); assertAllCommandsUsed(); } - } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index f22142073a..0b91513700 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -2461,7 +2461,7 @@ public class TestPlayer implements Player { } // card in battlefield - if (target instanceof TargetCardInGraveyardOrBattlefield) { + if (target instanceof TargetCardInGraveyardBattlefieldOrStack) { TargetCard targetFull = (TargetCard) target; for (String targetDefinition : targets) { checkTargetDefinitionMarksSupport(target, targetDefinition, "^"); @@ -2489,7 +2489,7 @@ public class TestPlayer implements Player { if (target.getOriginalTarget() instanceof TargetCardInOpponentsGraveyard || target.getOriginalTarget() instanceof TargetCardInYourGraveyard || target.getOriginalTarget() instanceof TargetCardInGraveyard - || target.getOriginalTarget() instanceof TargetCardInGraveyardOrBattlefield + || target.getOriginalTarget() instanceof TargetCardInGraveyardBattlefieldOrStack || (target.getOriginalTarget() instanceof TargetCard && target.getOriginalTarget().getZone() == Zone.GRAVEYARD)) { targetCardZonesChecked.add(Zone.GRAVEYARD); TargetCard targetFull = (TargetCard) target.getOriginalTarget(); diff --git a/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java new file mode 100644 index 0000000000..4878d28e1d --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java @@ -0,0 +1,54 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +/** + * @author TheElk801 + */ +public class PutOnTopOrBottomLibraryTargetEffect extends OneShotEffect { + + public PutOnTopOrBottomLibraryTargetEffect() { + this(""); + } + + public PutOnTopOrBottomLibraryTargetEffect(String text) { + super(Outcome.Benefit); + staticText = text; + } + + private PutOnTopOrBottomLibraryTargetEffect(final PutOnTopOrBottomLibraryTargetEffect effect) { + super(effect); + } + + @Override + public PutOnTopOrBottomLibraryTargetEffect copy() { + return new PutOnTopOrBottomLibraryTargetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(game.getOwnerId(source.getFirstTarget())); + if (player == null) { + return false; + } + boolean onTop = player.chooseUse( + Outcome.Detriment, "Put the targeted object on the top or bottom of your library?", + null, "Top", "Bottom", source, game + ); + return new PutOnLibraryTargetEffect(onTop).apply(game, source); + } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + return "the owner of target " + mode.getTargets().get(0).getTargetName() + + " puts it on the top or bottom of their library"; + } +} diff --git a/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java b/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java new file mode 100644 index 0000000000..fa01ba0207 --- /dev/null +++ b/Mage/src/main/java/mage/target/common/TargetCardInGraveyardBattlefieldOrStack.java @@ -0,0 +1,127 @@ +package mage.target.common; + +import mage.MageObject; +import mage.abilities.Ability; +import mage.constants.ComparisonType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; +import mage.target.TargetCard; + +import java.util.Set; +import java.util.UUID; + +/** + * @author LevelX2 + */ +public class TargetCardInGraveyardBattlefieldOrStack extends TargetCard { + + private static final FilterSpell defaultSpellFilter = new FilterSpell(); + + static { + defaultSpellFilter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, -1)); + } + + protected final FilterPermanent filterPermanent; + protected final FilterSpell filterSpell; + + public TargetCardInGraveyardBattlefieldOrStack(int minNumTargets, int maxNumTargets, FilterCard filterGraveyard, FilterPermanent filterBattlefield) { + this(minNumTargets, maxNumTargets, filterGraveyard, filterBattlefield, defaultSpellFilter, null); + } + + public TargetCardInGraveyardBattlefieldOrStack(int minNumTargets, int maxNumTargets, FilterCard filterGraveyard, FilterPermanent filterBattlefield, FilterSpell filterSpell, String targetName) { + super(minNumTargets, maxNumTargets, Zone.GRAVEYARD, filterGraveyard); // zone for card in graveyard, don't change + this.filterPermanent = filterBattlefield; + this.filterSpell = filterSpell; + this.targetName = targetName != null ? targetName : filter.getMessage() + + " in a graveyard " + + (maxNumTargets > 1 ? " and/or " : " or ") + + this.filterPermanent.getMessage() + + " on the battlefield"; + } + + public TargetCardInGraveyardBattlefieldOrStack(final TargetCardInGraveyardBattlefieldOrStack target) { + super(target); + this.filterPermanent = target.filterPermanent; + this.filterSpell = target.filterSpell; + } + + @Override + public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { + if (super.canChoose(sourceControllerId, source, game)) { + return true; + } + MageObject targetSource = game.getObject(source); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, sourceControllerId, source, game)) { + if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) { + return true; + } + } + for (StackObject stackObject : game.getStack()) { + if (stackObject instanceof Spell + && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) + && filterSpell.match(stackObject, sourceControllerId, source, game)) { + return true; + } + } + return false; + } + + @Override + public boolean canTarget(UUID id, Ability source, Game game) { + return this.canTarget(source.getControllerId(), id, source, game); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (super.canTarget(playerId, id, source, game)) { // in graveyard first + return true; + } + Permanent permanent = game.getPermanent(id); + if (permanent != null) { + return filterPermanent.match(permanent, playerId, source, game); + } + Spell spell = game.getSpell(id); + return spell != null && filterSpell.match(spell, playerId, source, game); + } + + @Override + public boolean canTarget(UUID id, Game game) { + return this.canTarget(null, id, null, game); + } + + @Override + public Set possibleTargets(UUID sourceControllerId, Game game) { + return this.possibleTargets(sourceControllerId, (Ability) null, game); + } + + @Override + public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { + Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); // in graveyard first + MageObject targetSource = game.getObject(source); + for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, sourceControllerId, source, game)) { + if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) { + possibleTargets.add(permanent.getId()); + } + } + for (StackObject stackObject : game.getStack()) { + if (stackObject instanceof Spell + && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) + && filterSpell.match(stackObject, sourceControllerId, source, game)) { + possibleTargets.add(stackObject.getId()); + } + } + return possibleTargets; + } + + @Override + public TargetCardInGraveyardBattlefieldOrStack copy() { + return new TargetCardInGraveyardBattlefieldOrStack(this); + } +} diff --git a/Mage/src/main/java/mage/target/common/TargetCardInGraveyardOrBattlefield.java b/Mage/src/main/java/mage/target/common/TargetCardInGraveyardOrBattlefield.java deleted file mode 100644 index 09d692361c..0000000000 --- a/Mage/src/main/java/mage/target/common/TargetCardInGraveyardOrBattlefield.java +++ /dev/null @@ -1,113 +0,0 @@ -package mage.target.common; - -import mage.MageObject; -import mage.abilities.Ability; -import mage.constants.Zone; -import mage.filter.FilterCard; -import mage.filter.FilterPermanent; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.target.TargetCard; - -import java.util.Set; -import java.util.UUID; - -/** - * @author LevelX2 - */ -public class TargetCardInGraveyardOrBattlefield extends TargetCard { - - protected final FilterPermanent filterBattlefield; - - public TargetCardInGraveyardOrBattlefield(int minNumTargets, int maxNumTargets, FilterCard filterGraveyard, FilterPermanent filterBattlefield) { - super(minNumTargets, maxNumTargets, Zone.GRAVEYARD, filterGraveyard); // zone for card in graveyard, don't change - this.filterBattlefield = filterBattlefield; - this.targetName = filter.getMessage() - + " in a graveyard " - + (maxNumTargets > 1 ? " and/or " : " or ") - + this.filterBattlefield.getMessage() - + " on the battlefield"; - } - - public TargetCardInGraveyardOrBattlefield(final TargetCardInGraveyardOrBattlefield target) { - super(target); - this.filterBattlefield = target.filterBattlefield; - } - - @Override - public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { - if (!super.canChoose(sourceControllerId, source, game)) { - MageObject targetSource = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterBattlefield, sourceControllerId, game)) { - if ((notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) - && filterBattlefield.match(permanent, sourceControllerId, source, game)) { - return true; - } - } - return false; - } - return true; - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - if (!super.canTarget(id, source, game)) { // in graveyard first - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - return filterBattlefield.match(permanent, source.getControllerId(), source, game); - } - } - return true; - } - - @Override - public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { - if (!super.canTarget(playerId, id, source, game)) { // in graveyard first - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - return filterBattlefield.match(permanent, playerId, source, game); - } - } - return true; - } - - @Override - public boolean canTarget(UUID id, Game game) { - if (!super.canTarget(id, game)) { // in graveyard first - Permanent permanent = game.getPermanent(id); - if (permanent != null) { - return filterBattlefield.match(permanent, game); - } - } - return true; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Game game) { - Set possibleTargets = super.possibleTargets(sourceControllerId, game); // in graveyard first - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterBattlefield, sourceControllerId, game)) { - if (filterBattlefield.match(permanent, sourceControllerId, null, game)) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; - } - - @Override - public Set possibleTargets(UUID sourceControllerId, Ability source, Game game) { - Set possibleTargets = super.possibleTargets(sourceControllerId, source, game); // in graveyard first - MageObject targetSource = game.getObject(source); - for (Permanent permanent : game.getBattlefield().getActivePermanents(filterBattlefield, sourceControllerId, source, game)) { - if ((notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) && filterBattlefield.match(permanent, sourceControllerId, source, game)) { - possibleTargets.add(permanent.getId()); - } - } - return possibleTargets; - } - - @Override - public TargetCardInGraveyardOrBattlefield copy() { - return new TargetCardInGraveyardOrBattlefield(this); - } - -}