From 0b6a2b25460acbeeda575ca8564b44fef8e3e44d Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Sun, 25 May 2025 17:50:07 +0200 Subject: [PATCH] refactor and test 'Scry X' effects Fixes #13667 --- .../mage/cards/a/AlibouAncientWitness.java | 83 +++++-------------- Mage.Sets/src/mage/cards/b/BrainInAJar.java | 35 +------- Mage.Sets/src/mage/cards/c/CascadeSeer.java | 43 ++-------- .../src/mage/cards/d/DivinersPortent.java | 6 +- .../src/mage/cards/e/ErestorOfTheCouncil.java | 13 ++- .../mage/cards/g/GnostroVoiceOfTheCrags.java | 45 +++------- Mage.Sets/src/mage/cards/g/GravenLore.java | 10 +-- .../src/mage/cards/n/NetheresePuzzleWard.java | 5 +- Mage.Sets/src/mage/cards/o/OathOfJace.java | 58 ++++--------- .../src/mage/cards/s/SianiEyeOfTheStorm.java | 58 ++++--------- .../src/mage/cards/s/SpotterThopter.java | 40 ++------- Mage.Sets/src/mage/cards/t/TheScarabGod.java | 72 ++++------------ .../src/mage/cards/t/TymaretCallsTheDead.java | 53 ++++-------- .../cards/single/bro/SpotterThopterTest.java | 66 +++++++++++++++ .../single/c21/AlibouAncientWitnessTest.java | 49 +++++++++++ .../single/cmr/SianiEyeOfTheStormTest.java | 49 +++++++++++ .../cards/single/hou/TheScarabGodTest.java | 41 +++++++++ .../test/cards/single/khm/GravenLoreTest.java | 40 +++++++++ .../test/cards/single/ogw/OathOfJaceTest.java | 42 ++++++++++ .../cards/single/sir/BrainInAJarTest.java | 49 +++++++++++ .../abilities/effects/keyword/ScryEffect.java | 55 +++++++----- 21 files changed, 505 insertions(+), 407 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/bro/SpotterThopterTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/c21/AlibouAncientWitnessTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/SianiEyeOfTheStormTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/hou/TheScarabGodTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/khm/GravenLoreTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/OathOfJaceTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/sir/BrainInAJarTest.java diff --git a/Mage.Sets/src/mage/cards/a/AlibouAncientWitness.java b/Mage.Sets/src/mage/cards/a/AlibouAncientWitness.java index df3188f77db..4517e2ec79c 100644 --- a/Mage.Sets/src/mage/cards/a/AlibouAncientWitness.java +++ b/Mage.Sets/src/mage/cards/a/AlibouAncientWitness.java @@ -4,22 +4,24 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledArtifactPermanent; import mage.filter.predicate.permanent.TappedPredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetAnyTarget; import java.util.UUID; @@ -29,6 +31,15 @@ import java.util.UUID; */ public final class AlibouAncientWitness extends CardImpl { + private static final FilterPermanent filter = new FilterControlledArtifactPermanent("tapped artifacts you control"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, null); + private static final Hint hint = new ValueHint("Tapped artifacts you control", xValue); + public AlibouAncientWitness(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{R}{W}"); @@ -45,11 +56,12 @@ public final class AlibouAncientWitness extends CardImpl { // Whenever one or more artifact creatures you control attack, Alibou, Ancient Witness deals X damage to any target and you scry X, where X is the number of tapped artifacts you control. Ability ability = new AttacksWithCreaturesTriggeredAbility( - new AlibouAncientWitnessEffect(), 1, - StaticFilters.FILTER_PERMANENTS_ARTIFACT_CREATURE + new DamageTargetEffect(xValue).setText("{this} deals X damage to any target"), + 1, StaticFilters.FILTER_PERMANENTS_ARTIFACT_CREATURE ).setTriggerPhrase("Whenever one or more artifact creatures you control attack, "); + ability.addEffect(new ScryEffect(xValue).concatBy("and you")); ability.addTarget(new TargetAnyTarget()); - this.addAbility(ability.addHint(AlibouAncientWitnessEffect.getHint())); + this.addAbility(ability.addHint(hint)); } private AlibouAncientWitness(final AlibouAncientWitness card) { @@ -60,57 +72,4 @@ public final class AlibouAncientWitness extends CardImpl { public AlibouAncientWitness copy() { return new AlibouAncientWitness(this); } -} - -class AlibouAncientWitnessEffect extends OneShotEffect { - - private static final FilterPermanent filter = new FilterControlledArtifactPermanent(); - - static { - filter.add(TappedPredicate.TAPPED); - } - - private static final Hint hint = new ValueHint( - "Tapped artifacts you control", new PermanentsOnBattlefieldCount(filter) - ); - - AlibouAncientWitnessEffect() { - super(Outcome.Benefit); - staticText = "{this} deals X damage to any target and you scry X, " + - "where X is the number of tapped artifacts you control"; - } - - private AlibouAncientWitnessEffect(final AlibouAncientWitnessEffect effect) { - super(effect); - } - - @Override - public AlibouAncientWitnessEffect copy() { - return new AlibouAncientWitnessEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - int xValue = game.getBattlefield().count(filter, source.getControllerId(), source, game); - if (xValue < 1) { - return false; - } - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null) { - permanent.damage(xValue, source.getSourceId(), source, game); - } - Player player = game.getPlayer(source.getFirstTarget()); - if (player != null) { - player.damage(xValue, source.getSourceId(), source, game); - } - player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.scry(xValue, source, game); - } - return true; - } - - public static Hint getHint() { - return hint; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/b/BrainInAJar.java b/Mage.Sets/src/mage/cards/b/BrainInAJar.java index 60e702b582a..ead1917f3c6 100644 --- a/Mage.Sets/src/mage/cards/b/BrainInAJar.java +++ b/Mage.Sets/src/mage/cards/b/BrainInAJar.java @@ -8,6 +8,7 @@ import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.keyword.ScryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -44,7 +45,7 @@ public final class BrainInAJar extends CardImpl { this.addAbility(ability); // {3}, {T}, Remove X charge counters from Brain in a Jar: Scry X. - ability = new SimpleActivatedAbility(new BrainInAJarScryEffect(), new GenericManaCost(3)); + ability = new SimpleActivatedAbility(new ScryEffect(GetXValue.instance), new GenericManaCost(3)); ability.addCost(new TapSourceCost()); ability.addCost(new RemoveVariableCountersSourceCost(CounterType.CHARGE)); this.addAbility(ability); @@ -90,34 +91,4 @@ class BrainInAJarCastEffect extends OneShotEffect { filter.add(new ManaValuePredicate(ComparisonType.EQUAL_TO, counters)); return CardUtil.castSpellWithAttributesForFree(controller, source, game, controller.getHand(), filter); } -} - -class BrainInAJarScryEffect extends OneShotEffect { - - BrainInAJarScryEffect() { - super(Outcome.Benefit); - this.staticText = "Scry X"; - } - - private BrainInAJarScryEffect(final BrainInAJarScryEffect effect) { - super(effect); - } - - @Override - public BrainInAJarScryEffect copy() { - return new BrainInAJarScryEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - int x = GetXValue.instance.calculate(game, source, this); - if (x > 0) { - return controller.scry(x, source, game); - } - return true; - } - return false; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/CascadeSeer.java b/Mage.Sets/src/mage/cards/c/CascadeSeer.java index c77dc5f960f..21096b446e9 100644 --- a/Mage.Sets/src/mage/cards/c/CascadeSeer.java +++ b/Mage.Sets/src/mage/cards/c/CascadeSeer.java @@ -1,18 +1,14 @@ package mage.cards.c; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.dynamicvalue.common.PartyCount; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.hint.common.PartyCountHint; 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 java.util.UUID; @@ -20,7 +16,6 @@ import java.util.UUID; * @author TheElk801 */ public final class CascadeSeer extends CardImpl { - public CascadeSeer(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); @@ -30,9 +25,14 @@ public final class CascadeSeer extends CardImpl { this.toughness = new MageInt(3); // When Cascade Seer enters the battlefield, scry X, where X is the number of creatures in your party. - this.addAbility(new EntersBattlefieldTriggeredAbility(new CascadeSeerEffect()).addHint(PartyCountHint.instance)); + this.addAbility( + new EntersBattlefieldTriggeredAbility( + new ScryEffect(PartyCount.instance) + .setText("scry X, where X is the number of creatures in your party") + ).addHint(PartyCountHint.instance)); } + private CascadeSeer(final CascadeSeer card) { super(card); } @@ -41,31 +41,4 @@ public final class CascadeSeer extends CardImpl { public CascadeSeer copy() { return new CascadeSeer(this); } -} - -class CascadeSeerEffect extends OneShotEffect { - - CascadeSeerEffect() { - super(Outcome.Benefit); - staticText = "scry X, where X is the number of creatures in your party. " + PartyCount.getReminder(); - } - - private CascadeSeerEffect(final CascadeSeerEffect effect) { - super(effect); - } - - @Override - public CascadeSeerEffect copy() { - return new CascadeSeerEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - int partyCount = PartyCount.instance.calculate(game, source, this); - return partyCount > 0 && player.scry(partyCount, source, game); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/d/DivinersPortent.java b/Mage.Sets/src/mage/cards/d/DivinersPortent.java index 4efb58a46f3..ab3c4cbacbc 100644 --- a/Mage.Sets/src/mage/cards/d/DivinersPortent.java +++ b/Mage.Sets/src/mage/cards/d/DivinersPortent.java @@ -6,6 +6,7 @@ import mage.abilities.dynamicvalue.common.GetXValue; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.RollDieWithResultTableEffect; +import mage.abilities.effects.keyword.ScryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -35,7 +36,10 @@ public final class DivinersPortent extends CardImpl { effect.addTableEntry(1, 14, new DrawCardSourceControllerEffect(GetXValue.instance)); // 15+ | Scry X, then draw X cards. - effect.addTableEntry(15, Integer.MAX_VALUE, new DivinersPortentEffect()); + effect.addTableEntry(15, Integer.MAX_VALUE, + new ScryEffect(GetXValue.instance), + new DrawCardSourceControllerEffect(GetXValue.instance).concatBy(", then") + ); } private DivinersPortent(final DivinersPortent card) { diff --git a/Mage.Sets/src/mage/cards/e/ErestorOfTheCouncil.java b/Mage.Sets/src/mage/cards/e/ErestorOfTheCouncil.java index 9e2f1195c0f..770ed22a932 100644 --- a/Mage.Sets/src/mage/cards/e/ErestorOfTheCouncil.java +++ b/Mage.Sets/src/mage/cards/e/ErestorOfTheCouncil.java @@ -5,6 +5,7 @@ import mage.abilities.Ability; import mage.abilities.common.FinishVotingTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.keyword.ScryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -13,7 +14,6 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.game.Game; import mage.game.permanent.token.TreasureToken; -import mage.players.Player; import java.util.Set; import java.util.UUID; @@ -68,19 +68,16 @@ class ErestorOfTheCouncilEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Set playerIds = (Set) getValue("votedAgainst"); - int count = 0; + int scryCount = 0; for (UUID opponentId : game.getOpponents(source.getControllerId())) { if (playerIds.contains(opponentId)) { - count++; + scryCount++; } else { new TreasureToken().putOntoBattlefield(1, game, source, opponentId); } } - if (count > 0) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - player.scry(count, source, game); - } + if (scryCount > 0) { + new ScryEffect(scryCount).apply(game, source); } return true; } diff --git a/Mage.Sets/src/mage/cards/g/GnostroVoiceOfTheCrags.java b/Mage.Sets/src/mage/cards/g/GnostroVoiceOfTheCrags.java index 4452a3e2678..24b17831deb 100644 --- a/Mage.Sets/src/mage/cards/g/GnostroVoiceOfTheCrags.java +++ b/Mage.Sets/src/mage/cards/g/GnostroVoiceOfTheCrags.java @@ -7,18 +7,17 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; import mage.game.Game; -import mage.players.Player; import mage.target.common.TargetCreaturePermanent; import mage.watchers.common.CastSpellLastTurnWatcher; @@ -29,6 +28,8 @@ import java.util.UUID; */ public final class GnostroVoiceOfTheCrags extends CardImpl { + private static final Hint hint = new ValueHint("Number of spells you've cast this turn", GnostroVoiceOfTheCragsValue.instance); + public GnostroVoiceOfTheCrags(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}{W}"); @@ -39,8 +40,8 @@ public final class GnostroVoiceOfTheCrags extends CardImpl { // {T}: Choose one. X is the number of spells you've cast this turn. // • Scry X. - Ability ability = new SimpleActivatedAbility(new GnostroVoiceOfTheCragsEffect(), new TapSourceCost()); - ability.addHint(new ValueHint("Number of spells you've cast this turn", GnostroVoiceOfTheCragsValue.instance)); + Ability ability = new SimpleActivatedAbility(new ScryEffect(GnostroVoiceOfTheCragsValue.instance), new TapSourceCost()); + ability.addHint(hint); ability.getModes().setChooseText("choose one. X is the number of spells you've cast this turn."); // • Gnostro, Voice of the Crags deals X damage to target creature. @@ -78,36 +79,12 @@ enum GnostroVoiceOfTheCragsValue implements DynamicValue { return instance; } + public String toString() { + return "X"; + } + @Override public String getMessage() { return ""; } -} - -class GnostroVoiceOfTheCragsEffect extends OneShotEffect { - - GnostroVoiceOfTheCragsEffect() { - super(Outcome.Benefit); - staticText = "scry X"; - } - - private GnostroVoiceOfTheCragsEffect(final GnostroVoiceOfTheCragsEffect effect) { - super(effect); - } - - @Override - public GnostroVoiceOfTheCragsEffect copy() { - return new GnostroVoiceOfTheCragsEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - return player.scry( - GnostroVoiceOfTheCragsValue.instance.calculate(game, source, this), source, game - ); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/GravenLore.java b/Mage.Sets/src/mage/cards/g/GravenLore.java index 9b0f03830dd..ee3008377c0 100644 --- a/Mage.Sets/src/mage/cards/g/GravenLore.java +++ b/Mage.Sets/src/mage/cards/g/GravenLore.java @@ -2,6 +2,8 @@ package mage.cards.g; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.keyword.ScryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -60,10 +62,8 @@ class GravenLoreEffect extends OneShotEffect { return false; } int snow = ManaPaidSourceWatcher.getSnowPaid(source.getId(), game); - if (snow > 0) { - player.scry(snow, source, game); - } - player.drawCards(3, source, game); - return true; + boolean result = new ScryEffect(snow).apply(game, source); + result |= new DrawCardSourceControllerEffect(3).apply(game, source); + return result; } } diff --git a/Mage.Sets/src/mage/cards/n/NetheresePuzzleWard.java b/Mage.Sets/src/mage/cards/n/NetheresePuzzleWard.java index 7943a43dfb6..d9fc5f5756d 100644 --- a/Mage.Sets/src/mage/cards/n/NetheresePuzzleWard.java +++ b/Mage.Sets/src/mage/cards/n/NetheresePuzzleWard.java @@ -2,6 +2,7 @@ package mage.cards.n; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; @@ -67,7 +68,9 @@ class NetheresePuzzleWardEffect extends OneShotEffect { if (player == null) { return false; } - return player.scry(player.rollDice(outcome, source, game, 4), source, game); + int roll = player.rollDice(outcome, source, game, 4); + new ScryEffect(roll).apply(game, source); + return true; } } diff --git a/Mage.Sets/src/mage/cards/o/OathOfJace.java b/Mage.Sets/src/mage/cards/o/OathOfJace.java index dbfcd69cfa2..92471f6bd54 100644 --- a/Mage.Sets/src/mage/cards/o/OathOfJace.java +++ b/Mage.Sets/src/mage/cards/o/OathOfJace.java @@ -1,27 +1,32 @@ package mage.cards.o; -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.common.DrawDiscardControllerEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SuperType; -import mage.filter.StaticFilters; -import mage.game.Game; -import mage.players.Player; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPlaneswalkerPermanent; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class OathOfJace extends CardImpl { + private static final FilterPermanent filter = new FilterControlledPlaneswalkerPermanent("planeswalkers you control"); + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, null); + private static final Hint hint = new ValueHint("Planeswalkers you control", xValue); + public OathOfJace(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); this.supertype.add(SuperType.LEGENDARY); @@ -30,8 +35,7 @@ public final class OathOfJace extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawDiscardControllerEffect(3, 2), false)); // At the beginning of your upkeep, scry X, where X is the number of planeswalkers you control. - this.addAbility(new BeginningOfUpkeepTriggeredAbility(new OathOfJaceEffect())); - + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new ScryEffect(xValue)).addHint(hint)); } private OathOfJace(final OathOfJace card) { @@ -42,34 +46,4 @@ public final class OathOfJace extends CardImpl { public OathOfJace copy() { return new OathOfJace(this); } -} - -class OathOfJaceEffect extends OneShotEffect { - - OathOfJaceEffect() { - super(Outcome.DrawCard); - this.staticText = "scry X, where X is the number of planeswalkers you control"; - } - - private OathOfJaceEffect(final OathOfJaceEffect effect) { - super(effect); - } - - @Override - public OathOfJaceEffect copy() { - return new OathOfJaceEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - int planeswalker = game.getBattlefield().countAll(StaticFilters.FILTER_PERMANENT_PLANESWALKER, source.getControllerId(), game); - if (planeswalker > 0) { - controller.scry(planeswalker, source, game); - } - return true; - } - return false; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SianiEyeOfTheStorm.java b/Mage.Sets/src/mage/cards/s/SianiEyeOfTheStorm.java index e9ad36d3a34..066f53d975b 100644 --- a/Mage.Sets/src/mage/cards/s/SianiEyeOfTheStorm.java +++ b/Mage.Sets/src/mage/cards/s/SianiEyeOfTheStorm.java @@ -1,23 +1,23 @@ package mage.cards.s; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.AttacksTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValuePositiveHint; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.PartnerAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterPermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.filter.predicate.permanent.AttackingPredicate; -import mage.game.Game; -import mage.players.Player; import java.util.UUID; @@ -26,6 +26,16 @@ import java.util.UUID; */ public final class SianiEyeOfTheStorm extends CardImpl { + private static final FilterPermanent filter = new FilterCreaturePermanent("attacking creatures with flying"); + + static { + filter.add(new AbilityPredicate(FlyingAbility.class)); + filter.add(AttackingPredicate.instance); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, null); + private static final Hint hint = new ValuePositiveHint("Number of Rats you control", xValue); + public SianiEyeOfTheStorm(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); @@ -39,7 +49,7 @@ public final class SianiEyeOfTheStorm extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever Siani, Eye of the Storm attacks, scry X, where X is the number of attacking creatures with flying. - this.addAbility(new AttacksTriggeredAbility(new SianiEyeOfTheStormEffect(), false)); + this.addAbility(new AttacksTriggeredAbility(new ScryEffect(xValue)).addHint(hint)); // Partner this.addAbility(PartnerAbility.getInstance()); @@ -53,38 +63,4 @@ public final class SianiEyeOfTheStorm extends CardImpl { public SianiEyeOfTheStorm copy() { return new SianiEyeOfTheStorm(this); } -} - -class SianiEyeOfTheStormEffect extends OneShotEffect { - - private static final FilterPermanent filter = new FilterCreaturePermanent(); - - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); - filter.add(AttackingPredicate.instance); - } - - SianiEyeOfTheStormEffect() { - super(Outcome.Benefit); - staticText = "scry X, where X is the number of attacking creatures with flying"; - } - - private SianiEyeOfTheStormEffect(final SianiEyeOfTheStormEffect effect) { - super(effect); - } - - @Override - public SianiEyeOfTheStormEffect copy() { - return new SianiEyeOfTheStormEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - int count = game.getBattlefield().count(filter, source.getControllerId(), source, game); - return count > 0 && player.scry(count, source, game); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SpotterThopter.java b/Mage.Sets/src/mage/cards/s/SpotterThopter.java index 172dffb0c48..4b0faad6e7f 100644 --- a/Mage.Sets/src/mage/cards/s/SpotterThopter.java +++ b/Mage.Sets/src/mage/cards/s/SpotterThopter.java @@ -1,19 +1,15 @@ package mage.cards.s; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.PrototypeAbility; 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.game.permanent.Permanent; -import mage.players.Player; import java.util.UUID; @@ -36,7 +32,9 @@ public final class SpotterThopter extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Spotter Thopter enters the battlefield, scry X, where X is its power. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SpotterThopterEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new ScryEffect(SourcePermanentPowerValue.NOT_NEGATIVE) + .setText("scry X, where X is its power"))); } private SpotterThopter(final SpotterThopter card) { @@ -48,31 +46,3 @@ public final class SpotterThopter extends CardImpl { return new SpotterThopter(this); } } - -class SpotterThopterEffect extends OneShotEffect { - - SpotterThopterEffect() { - super(Outcome.Benefit); - staticText = "scry X, where X is its power"; - } - - private SpotterThopterEffect(final SpotterThopterEffect effect) { - super(effect); - } - - @Override - public SpotterThopterEffect copy() { - return new SpotterThopterEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = source.getSourcePermanentOrLKI(game); - if (player == null || permanent == null) { - return false; - } - int power = permanent.getPower().getValue(); - return power > 0 && player.scry(power, source, game); - } -} diff --git a/Mage.Sets/src/mage/cards/t/TheScarabGod.java b/Mage.Sets/src/mage/cards/t/TheScarabGod.java index 8e2ec515794..500c819a2ec 100644 --- a/Mage.Sets/src/mage/cards/t/TheScarabGod.java +++ b/Mage.Sets/src/mage/cards/t/TheScarabGod.java @@ -4,6 +4,8 @@ import mage.MageInt; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -31,7 +33,6 @@ import mage.target.targetpointer.FixedTarget; import java.util.UUID; /** - * * @author spjspj */ public final class TheScarabGod extends CardImpl { @@ -51,15 +52,17 @@ public final class TheScarabGod extends CardImpl { this.toughness = new MageInt(5); // At the beginning of your upkeep, each opponent loses X life and you scry X, where X is the number of Zombies you control. - this.addAbility(new BeginningOfUpkeepTriggeredAbility(new TheScarabGodEffect(xValue)).addHint(hint)); + Ability ability = new BeginningOfUpkeepTriggeredAbility(new LoseLifeOpponentsEffect(xValue).setText("each opponent loses X life")); + ability.addEffect(new ScryEffect(xValue).concatBy("and you")); + this.addAbility(ability.addHint(hint)); // {2}{U}{B}: Exile target creature card from a graveyard. Create a token that's a copy of it, except it's a 4/4 black Zombie. - Ability ability = new SimpleActivatedAbility(new TheScarabGodEffect2(), new ManaCostsImpl<>("{2}{U}{B}")); + ability = new SimpleActivatedAbility(new TheScarabGodExileEffect(), new ManaCostsImpl<>("{2}{U}{B}")); ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD_CREATURE_A_GRAVEYARD)); this.addAbility(ability); // When The Scarab God dies, return it to its owner's hand at the beginning of the next end step. - this.addAbility(new DiesSourceTriggeredAbility(new TheScarabGodEffect3())); + this.addAbility(new DiesSourceTriggeredAbility(new TheScarabGodEffectDieEffect())); } private TheScarabGod(final TheScarabGod card) { @@ -72,61 +75,20 @@ public final class TheScarabGod extends CardImpl { } } -class TheScarabGodEffect extends OneShotEffect { +class TheScarabGodExileEffect extends OneShotEffect { - private final DynamicValue numOfZombies; - - public TheScarabGodEffect(DynamicValue numOfZombies) { - super(Outcome.Benefit); - this.numOfZombies = numOfZombies; - staticText = "each opponent loses X life and you scry X, where X is the number of Zombies you control"; - } - - private TheScarabGodEffect(final TheScarabGodEffect effect) { - super(effect); - this.numOfZombies = effect.numOfZombies; - } - - @Override - public TheScarabGodEffect copy() { - return new TheScarabGodEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - int numZombies = numOfZombies.calculate(game, source, this); - if (numZombies > 0) { - for (UUID playerId : game.getOpponents(source.getControllerId())) { - Player opponent = game.getPlayer(playerId); - if (opponent != null) { - opponent.loseLife(numZombies, game, source, false); - } - } - controller.scry(numZombies, source, game); - } - - return true; - } - return false; - } -} - -class TheScarabGodEffect2 extends OneShotEffect { - - public TheScarabGodEffect2() { + public TheScarabGodExileEffect() { super(Outcome.PutCreatureInPlay); this.staticText = "Exile target creature card from a graveyard. Create a token that's a copy of it, except it's a 4/4 black Zombie."; } - private TheScarabGodEffect2(final TheScarabGodEffect2 effect) { + private TheScarabGodExileEffect(final TheScarabGodExileEffect effect) { super(effect); } @Override - public TheScarabGodEffect2 copy() { - return new TheScarabGodEffect2(this); + public TheScarabGodExileEffect copy() { + return new TheScarabGodExileEffect(this); } @Override @@ -147,16 +109,16 @@ class TheScarabGodEffect2 extends OneShotEffect { } } -class TheScarabGodEffect3 extends OneShotEffect { +class TheScarabGodEffectDieEffect extends OneShotEffect { private static final String effectText = "return it to its owner's hand at the beginning of the next end step"; - TheScarabGodEffect3() { + TheScarabGodEffectDieEffect() { super(Outcome.Benefit); staticText = effectText; } - private TheScarabGodEffect3(final TheScarabGodEffect3 effect) { + private TheScarabGodEffectDieEffect(final TheScarabGodEffectDieEffect effect) { super(effect); } @@ -172,7 +134,7 @@ class TheScarabGodEffect3 extends OneShotEffect { } @Override - public TheScarabGodEffect3 copy() { - return new TheScarabGodEffect3(this); + public TheScarabGodEffectDieEffect copy() { + return new TheScarabGodEffectDieEffect(this); } } diff --git a/Mage.Sets/src/mage/cards/t/TymaretCallsTheDead.java b/Mage.Sets/src/mage/cards/t/TymaretCallsTheDead.java index 6226f9e23aa..083bc824aef 100644 --- a/Mage.Sets/src/mage/cards/t/TymaretCallsTheDead.java +++ b/Mage.Sets/src/mage/cards/t/TymaretCallsTheDead.java @@ -2,15 +2,22 @@ package mage.cards.t; import mage.abilities.Ability; import mage.abilities.common.SagaAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterCard; import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.token.ZombieToken; @@ -25,6 +32,11 @@ import java.util.UUID; */ public final class TymaretCallsTheDead extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledCreaturePermanent(SubType.ZOMBIE, "Zombies you control"); + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, null); + private static final Hint hint = new ValueHint("Number of Rats you control", xValue); + public TymaretCallsTheDead(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); @@ -39,7 +51,10 @@ public final class TymaretCallsTheDead extends CardImpl { ); // III — You gain X life and scry X, where X is the number of Zombies you control. - sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new TymaretCallsTheDeadLastEffect()); + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, + new GainLifeEffect(xValue).setText("You gain X life"), + new ScryEffect(xValue).concatBy("and")); + sagaAbility.addHint(hint); this.addAbility(sagaAbility); } @@ -101,38 +116,4 @@ class TymaretCallsTheDeadFirstEffect extends OneShotEffect { return player.moveCards(game.getCard(target.getFirstTarget()), Zone.EXILED, source, game) && tokenEffect.apply(game, source); } -} - -class TymaretCallsTheDeadLastEffect extends OneShotEffect { - - private static final FilterPermanent filter = new FilterPermanent(SubType.ZOMBIE, ""); - - TymaretCallsTheDeadLastEffect() { - super(Outcome.Benefit); - staticText = "You gain X life and scry X, where X is the number of Zombies you control."; - } - - private TymaretCallsTheDeadLastEffect(final TymaretCallsTheDeadLastEffect effect) { - super(effect); - } - - @Override - public TymaretCallsTheDeadLastEffect copy() { - return new TymaretCallsTheDeadLastEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - int zombieCount = game.getBattlefield().countAll(filter, source.getControllerId(), game); - if (zombieCount <= 0) { - return true; - } - player.gainLife(zombieCount, game, source); - player.scry(zombieCount, source, game); - return true; - } -} +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/bro/SpotterThopterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/bro/SpotterThopterTest.java new file mode 100644 index 00000000000..eca868df272 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/bro/SpotterThopterTest.java @@ -0,0 +1,66 @@ +package org.mage.test.cards.single.bro; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SpotterThopterTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SpotterThopter Spotter Thopter} {8} + * Artifact Creature — Thopter + * Prototype {3}{U} — 2/3 (You may cast this spell with different mana cost, color, and size. It keeps its abilities and types.) + * Flying + * When this creature enters, scry X, where X is its power. + * 4/5 + */ + private static final String thopter = "Spotter Thopter"; + + @Test + public void test_prototype_scry2() { + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, "Ancient Amphitheater"); + addCard(Zone.LIBRARY, playerA, "Baleful Strix"); + addCard(Zone.LIBRARY, playerA, "Crucible of Worlds"); + addCard(Zone.LIBRARY, playerA, "Desecration Demon"); + + addCard(Zone.HAND, playerA, thopter, 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, thopter + " using Prototype"); + + // Oath of Jace's upkeep trigger triggers + addTarget(playerA, "Desecration Demon"); // put on bottom with scry 2 + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + } + + @Test + public void test_normal_scry4() { + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, "Ancient Amphitheater"); + addCard(Zone.LIBRARY, playerA, "Baleful Strix"); + addCard(Zone.LIBRARY, playerA, "Crucible of Worlds"); + addCard(Zone.LIBRARY, playerA, "Desecration Demon"); + + addCard(Zone.HAND, playerA, thopter, 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 8); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, thopter); + + // Oath of Jace's upkeep trigger triggers + addTarget(playerA, "Desecration Demon^Crucible of Worlds"); // put on bottom with scry 2 + setChoice(playerA, "Crucible of Worlds"); // order for bottom + setChoice(playerA, "Baleful Strix"); // order for top + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/AlibouAncientWitnessTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/AlibouAncientWitnessTest.java new file mode 100644 index 00000000000..ca0d5387a14 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/c21/AlibouAncientWitnessTest.java @@ -0,0 +1,49 @@ +package org.mage.test.cards.single.c21; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class AlibouAncientWitnessTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.a.AlibouAncientWitness Alibou, Ancient Witness {3}{R}{W} + * Legendary Artifact Creature — Golem + * Other artifact creatures you control have haste. + * Whenever one or more artifact creatures you control attack, Alibou deals X damage to any target and you scry X, where X is the number of tapped artifacts you control. + * 4/5 + */ + private static final String alibou = "Alibou, Ancient Witness"; + + @Test + public void test_scry2() { + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, "Ancient Amphitheater"); + addCard(Zone.LIBRARY, playerA, "Baleful Strix"); + + addCard(Zone.BATTLEFIELD, playerA, alibou); + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + addCard(Zone.BATTLEFIELD, playerA, "Ornithopter"); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Piker"); + addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard"); + + attack(1, playerA, alibou, playerB); + attack(1, playerA, "Memnite", playerB); + attack(1, playerA, "Goblin Piker", playerB); + attack(1, playerA, "Elite Vanguard", playerB); + + addTarget(playerA, playerB); // Alibou's trigger + addTarget(playerA, "Baleful Strix^Ancient Amphitheater"); // put on bottom with Scry 2 + setChoice(playerA, "Ancient Amphitheater"); // order for bottom + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 4 - 1 - 2 - 2 - 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/SianiEyeOfTheStormTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/SianiEyeOfTheStormTest.java new file mode 100644 index 00000000000..3ca53ca8e6d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/cmr/SianiEyeOfTheStormTest.java @@ -0,0 +1,49 @@ +package org.mage.test.cards.single.cmr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class SianiEyeOfTheStormTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.s.SianiEyeOfTheStorm Siani, Eye of the Storm} {3}{U} + * Legendary Creature — Djinn Monk + * Flying + * Whenever Siani attacks, scry X, where X is the number of attacking creatures with flying. + * Partner (You can have two commanders if both have partner.) + * 3/2 + */ + private static final String siani = "Siani, Eye of the Storm"; + + @Test + public void test_scry2() { + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, "Ancient Amphitheater"); + addCard(Zone.LIBRARY, playerA, "Baleful Strix"); + + addCard(Zone.BATTLEFIELD, playerA, siani); + addCard(Zone.BATTLEFIELD, playerA, "Air Elemental"); + addCard(Zone.BATTLEFIELD, playerA, "Abbey Griffin"); + addCard(Zone.BATTLEFIELD, playerA, "Goblin Piker"); + addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard"); + + attack(1, playerA, siani, playerB); + attack(1, playerA, "Air Elemental", playerB); + attack(1, playerA, "Goblin Piker", playerB); + attack(1, playerA, "Elite Vanguard", playerB); + + addTarget(playerA, "Baleful Strix^Ancient Amphitheater"); // put on bottom with Scry 2 + setChoice(playerA, "Ancient Amphitheater"); // order for bottom + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 3 - 4 - 2 - 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/TheScarabGodTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/TheScarabGodTest.java new file mode 100644 index 00000000000..e905244e7e0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/hou/TheScarabGodTest.java @@ -0,0 +1,41 @@ +package org.mage.test.cards.single.hou; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class TheScarabGodTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.t.TheScarabGod The Scarab God} {3}{U}{B} + * Legendary Creature — God + * At the beginning of your upkeep, each opponent loses X life and you scry X, where X is the number of Zombies you control. + * {2}{U}{B}: Exile target creature card from a graveyard. Create a token that’s a copy of it, except it’s a 4/4 black Zombie. + * When The Scarab God dies, return it to its owner’s hand at the beginning of the next end step. + * 5/5 + */ + private static final String god = "The Scarab God"; + + @Test + public void test_scry2() { + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, "Ancient Amphitheater"); + addCard(Zone.LIBRARY, playerA, "Baleful Strix"); + + addCard(Zone.BATTLEFIELD, playerA, god, 1); + addCard(Zone.BATTLEFIELD, playerA, "Legions of Lim-Dul", 2); + + // The Scarab God's upkeep trigger triggers + addTarget(playerA, "Ancient Amphitheater"); // put on bottom with scry 2 + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertLife(playerB, 20 - 2); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/khm/GravenLoreTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/khm/GravenLoreTest.java new file mode 100644 index 00000000000..a59da04619f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/khm/GravenLoreTest.java @@ -0,0 +1,40 @@ +package org.mage.test.cards.single.khm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class GravenLoreTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.g.GravenLore Graven Lore} {3}{U}{U} + * Snow Instant + * Scry X, where X is the amount of {S} spent to cast this spell, then draw three cards. ({S} is mana from a snow source.) + */ + private static final String lore = "Graven Lore"; + + @Test + public void test_scry2() { + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, "Ancient Amphitheater"); + addCard(Zone.LIBRARY, playerA, "Baleful Strix"); + + addCard(Zone.HAND, playerA, lore, 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Snow-Covered Island", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lore); + addTarget(playerA, "Baleful Strix^Ancient Amphitheater"); // put on bottom with Scry 2 + setChoice(playerA, "Ancient Amphitheater"); // order for bottom + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertHandCount(playerA, "Mountain", 3); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/OathOfJaceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/OathOfJaceTest.java new file mode 100644 index 00000000000..1f774b62d82 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ogw/OathOfJaceTest.java @@ -0,0 +1,42 @@ +package org.mage.test.cards.single.ogw; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class OathOfJaceTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.o.OathOfJace Oath of Jace} {2}{U} + * Legendary Enchantment + * When Oath of Jace enters, draw three cards, then discard two cards. + * At the beginning of your upkeep, scry X, where X is the number of planeswalkers you control. + */ + private static final String oath = "Oath of Jace"; + + @Test + public void test_scry2() { + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, "Ancient Amphitheater"); + addCard(Zone.LIBRARY, playerA, "Baleful Strix"); + + addCard(Zone.BATTLEFIELD, playerA, oath, 1); + addCard(Zone.BATTLEFIELD, playerA, "Legions of Lim-Dul", 2); // not a planeswalker + addCard(Zone.BATTLEFIELD, playerA, "Chandra Nalaar"); + addCard(Zone.BATTLEFIELD, playerA, "Jace Beleren"); + + addCard(Zone.BATTLEFIELD, playerB, "Ajani, Caller of the Pride"); // not in your control + + // Oath of Jace's upkeep trigger triggers + addTarget(playerA, "Ancient Amphitheater^Baleful Strix"); // put on bottom with scry 2 + setChoice(playerA, "Baleful Strix"); // ordering the bottom + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.PRECOMBAT_MAIN); + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/sir/BrainInAJarTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/sir/BrainInAJarTest.java new file mode 100644 index 00000000000..05e353eca1c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/sir/BrainInAJarTest.java @@ -0,0 +1,49 @@ +package org.mage.test.cards.single.sir; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class BrainInAJarTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.b.BrainInAJar Brain in a Jar} {2} + * Artifact + * {1}, {T}: Put a charge counter on this artifact, then you may cast an instant or sorcery spell with mana value equal to the number of charge counters on this artifact from your hand without paying its mana cost. + * {3}, {T}, Remove X charge counters from this artifact: Scry X. + */ + private static final String brain = "Brain in a Jar"; + + @Test + public void test_scry2() { + skipInitShuffling(); + addCard(Zone.LIBRARY, playerA, "Ancient Amphitheater"); + addCard(Zone.LIBRARY, playerA, "Baleful Strix"); + + addCard(Zone.BATTLEFIELD, playerA, brain, 1); + addCard(Zone.BATTLEFIELD, playerA, "Dragon Appeasement"); // enchantement for skip draw + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, {T}"); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, {T}"); + + activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, {T}"); + + activateAbility(7, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}, {T}"); + setChoice(playerA, "X=2"); // how many counters to remove + addTarget(playerA, "Baleful Strix^Ancient Amphitheater"); // put on bottom with Scry 2 + setChoice(playerA, "Ancient Amphitheater"); // order for bottom + + setStrictChooseMode(true); + setStopAt(7, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertCounterCount(playerA, brain, CounterType.CHARGE, 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/ScryEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/ScryEffect.java index d4bdf682bae..f1c7ffae874 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/ScryEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/ScryEffect.java @@ -1,6 +1,8 @@ package mage.abilities.effects.keyword; import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; import mage.game.Game; @@ -8,37 +10,45 @@ import mage.players.Player; import mage.util.CardUtil; /** - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, Susucr */ public class ScryEffect extends OneShotEffect { - protected final int scryNumber; + protected DynamicValue amount; protected final boolean showEffectHint; public ScryEffect(int scryNumber) { this(scryNumber, true); } + public ScryEffect(DynamicValue amount) { + this(amount, false); + } + public ScryEffect(int scryNumber, boolean showEffectHint) { + this(StaticValue.get(scryNumber), showEffectHint); + } + + public ScryEffect(DynamicValue amount, boolean showEffectHint) { super(Outcome.Benefit); - this.scryNumber = scryNumber; + this.amount = amount.copy(); this.showEffectHint = showEffectHint; - this.setText(); + this.staticText = generateText(); } protected ScryEffect(final ScryEffect effect) { super(effect); - this.scryNumber = effect.scryNumber; + this.amount = effect.amount.copy(); this.showEffectHint = effect.showEffectHint; } @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - return player.scry(scryNumber, source, game); + if (player == null) { + return false; } - return false; + return player.scry(this.amount.calculate(game, source, this), source, game); } @Override @@ -46,19 +56,24 @@ public class ScryEffect extends OneShotEffect { return new ScryEffect(this); } - private void setText() { - StringBuilder sb = new StringBuilder("scry ").append(scryNumber); - if (!showEffectHint) { - staticText = sb.toString(); - return; + private String generateText() { + StringBuilder sb = new StringBuilder("scry "); + String value = amount.toString(); + sb.append(CardUtil.numberToText(value)); + String message = amount.getMessage(); + if (!message.isEmpty()) { + sb.append(value.equals("X") ? ", where X is " : " for each "); + sb.append(message); } - if (scryNumber == 1) { - sb.append(". (Look at the top card of your library. You may put that card on the bottom.)"); - } else { - sb.append(". (Look at the top "); - sb.append(CardUtil.numberToText(scryNumber)); - sb.append(" cards of your library, then put any number of them on the bottom and the rest on top in any order.)"); + if (showEffectHint) { + if (value == "1") { + sb.append(". (Look at the top card of your library. You may put that card on the bottom.)"); + } else { + sb.append(". (Look at the top "); + sb.append(CardUtil.numberToText(value)); + sb.append(" cards of your library, then put any number of them on the bottom and the rest on top in any order.)"); + } } - staticText = sb.toString(); + return sb.toString(); } }