diff --git a/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java b/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java index 4317d671c37..a30c497f268 100644 --- a/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java +++ b/Mage.Sets/src/mage/cards/s/SageOfTheBeyond.java @@ -5,17 +5,12 @@ import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ForetellAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.predicate.ObjectSourcePlayer; -import mage.filter.predicate.ObjectSourcePlayerPredicate; -import mage.game.Game; -import mage.game.stack.Spell; +import mage.filter.predicate.other.SpellCastFromAnywhereOtherThanHand; import java.util.UUID; @@ -27,7 +22,7 @@ public final class SageOfTheBeyond extends CardImpl { private static final FilterCard filter = new FilterCard(); static { - filter.add(SageOfTheBeyondPredicate.instance); + filter.add(SpellCastFromAnywhereOtherThanHand.instance); } public SageOfTheBeyond(UUID ownerId, CardSetInfo setInfo) { @@ -57,19 +52,4 @@ public final class SageOfTheBeyond extends CardImpl { public SageOfTheBeyond copy() { return new SageOfTheBeyond(this); } -} - -enum SageOfTheBeyondPredicate implements ObjectSourcePlayerPredicate { - instance; - - @Override - public boolean apply(ObjectSourcePlayer input, Game game) { - if (input.getObject() instanceof Spell) { - return !input.getObject().isOwnedBy(input.getPlayerId()) - || !Zone.HAND.match(((Spell) input.getObject()).getFromZone()); - } else { - return !input.getObject().isOwnedBy(input.getPlayerId()) - || !Zone.HAND.match(game.getState().getZone(input.getObject().getId())); - } - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SavvyTrader.java b/Mage.Sets/src/mage/cards/s/SavvyTrader.java new file mode 100644 index 00000000000..b860a6e8ab6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SavvyTrader.java @@ -0,0 +1,109 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.predicate.other.SpellCastFromAnywhereOtherThanHand; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class SavvyTrader extends CardImpl { + + private static final FilterCard filter = new FilterCard(); + + static { + filter.add(SpellCastFromAnywhereOtherThanHand.instance); + } + + public SavvyTrader(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.CITIZEN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When Savvy Trader enters the battlefield, exile target permanent card from your graveyard. You may play that card for as long as it remains exiled. + Ability ability = new EntersBattlefieldTriggeredAbility(new SavvyTraderEffect()); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_PERMANENT)); + this.addAbility(ability); + + // Spells you cast from anywhere other than your hand cost {1} less to cast. + this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1) + .setText("Spells you cast from anywhere other than your hand cost {1} less to cast."))); + } + + private SavvyTrader(final SavvyTrader card) { + super(card); + } + + @Override + public SavvyTrader copy() { + return new SavvyTrader(this); + } +} + +class SavvyTraderEffect extends OneShotEffect { + + SavvyTraderEffect() { + super(Outcome.DrawCard); + staticText = "exile target permanent card from your graveyard. " + + "You may play that card for as long as it remains exiled"; + } + + private SavvyTraderEffect(final SavvyTraderEffect effect) { + super(effect); + } + + @Override + public SavvyTraderEffect copy() { + return new SavvyTraderEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); + Card card = game.getCard(source.getFirstTarget()); + if (sourcePermanent == null || controller == null || card == null) { + return false; + } + // One single exile zone per player is enough for this effect. source does not matter. + // TODO: have a rework to group together in that same exile zone all cards in exile that + // - are not linked to any other ability (like return on some condition / be counted by some effet) + // - can be played by a single player until end of game + // On a more broad subject, there is a bunch of improvements we could do to exile zone management. + String keyForPlayer = "Shared::EndOfGame::PlayerMayPlay=" + controller.getId(); + UUID exileId = CardUtil.getExileZoneId(keyForPlayer, game); + String exileName = controller.getName() + " may play until end of game"; + if (controller.moveCardsToExile(card, source, game, true, exileId, exileName)) { + ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect(Duration.EndOfGame); + effect.setTargetPointer(new FixedTarget(card, game)); + game.addEffect(effect, source); + } + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java index 00f7a7c8279..93f9802a138 100644 --- a/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java +++ b/Mage.Sets/src/mage/sets/OutlawsOfThunderJunctionCommander.java @@ -220,6 +220,7 @@ public final class OutlawsOfThunderJunctionCommander extends ExpansionSet { cards.add(new SetCardInfo("Sage of the Beyond", 111, Rarity.RARE, mage.cards.s.SageOfTheBeyond.class)); cards.add(new SetCardInfo("Sand Scout", 11, Rarity.RARE, mage.cards.s.SandScout.class)); cards.add(new SetCardInfo("Satyr Wayfinder", 204, Rarity.COMMON, mage.cards.s.SatyrWayfinder.class)); + cards.add(new SetCardInfo("Savvy Trader", 33, Rarity.RARE, mage.cards.s.SavvyTrader.class)); cards.add(new SetCardInfo("Scaretiller", 266, Rarity.COMMON, mage.cards.s.Scaretiller.class)); cards.add(new SetCardInfo("Scattered Groves", 315, Rarity.RARE, mage.cards.s.ScatteredGroves.class)); cards.add(new SetCardInfo("Scavenger Grounds", 316, Rarity.RARE, mage.cards.s.ScavengerGrounds.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/SavvyTraderTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/SavvyTraderTest.java new file mode 100644 index 00000000000..35d5ff706a7 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/SavvyTraderTest.java @@ -0,0 +1,65 @@ +package org.mage.test.cards.single.ltr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SavvyTraderTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SavvyTrader Savvy Trader} + * Savvy Trader {3}{G} + * Creature — Human Citizen + * When Savvy Trader enters the battlefield, exile target permanent card from your graveyard. You may play that card for as long as it remains exiled. + * Spells you cast from anywhere other than your hand cost {1} less to cast. + * 3/3 + */ + private static final String trader = "Savvy Trader"; + + @Test + public void test_Play_Baneslayer() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, trader, 1); + addCard(Zone.GRAVEYARD, playerA, "Baneslayer Angel", 1); + addCard(Zone.BATTLEFIELD, playerA, "Savannah", 4); // 4 is enough to pay for Angel with trader reduction. + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, trader); + addTarget(playerA, "Baneslayer Angel"); + + checkExileCount("Angel got exiled", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Baneslayer Angel", 1); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Baneslayer Angel"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertTappedCount("Savannah", true, 4); + assertPermanentCount(playerA, "Baneslayer Angel", 1); + } + + @Test + public void test_Play_Swamp() { + setStrictChooseMode(true); + + addCard(Zone.HAND, playerA, trader, 1); + addCard(Zone.GRAVEYARD, playerA, "Swamp", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, trader); + addTarget(playerA, "Swamp"); + + checkExileCount("Swamp got exiled", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Swamp", 1); + + playLand(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp"); + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Swamp", 1); + } +} diff --git a/Mage/src/main/java/mage/filter/predicate/other/SpellCastFromAnywhereOtherThanHand.java b/Mage/src/main/java/mage/filter/predicate/other/SpellCastFromAnywhereOtherThanHand.java new file mode 100644 index 00000000000..6938a667b33 --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/other/SpellCastFromAnywhereOtherThanHand.java @@ -0,0 +1,23 @@ +package mage.filter.predicate.other; + +import mage.cards.Card; +import mage.constants.Zone; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.stack.Spell; + +public enum SpellCastFromAnywhereOtherThanHand implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + if (input.getObject() instanceof Spell) { + return !input.getObject().isOwnedBy(input.getPlayerId()) + || !Zone.HAND.match(((Spell) input.getObject()).getFromZone()); + } else { + return !input.getObject().isOwnedBy(input.getPlayerId()) + || !Zone.HAND.match(game.getState().getZone(input.getObject().getId())); + } + } +}