From d54e1c6eac2512d092823007262247115039c846 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Tue, 23 Mar 2021 19:44:45 -0400 Subject: [PATCH] [DTK] updated implementation of spells which reveal dragon cards as a cost --- Mage.Sets/src/mage/cards/d/DraconicRoar.java | 69 ++----- .../mage/cards/d/DragonlordsPrerogative.java | 82 ++------ .../mage/cards/f/FoulTongueInvocation.java | 79 ++------ .../src/mage/cards/o/OratorOfOjutai.java | 122 ++---------- .../src/mage/cards/s/ScaleguardSentinels.java | 64 ++----- .../src/mage/cards/s/SilumgarsScorn.java | 102 ++-------- .../RevealedOrControlledDragonTest.java | 181 ++++++++++++++++++ .../RevealedOrControlledDragonCondition.java | 30 +++ .../common/RevealDragonFromHandCost.java | 58 ++++++ ...heBattlefieldWhileSpellWasCastWatcher.java | 56 +++--- 10 files changed, 377 insertions(+), 466 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RevealedOrControlledDragonTest.java create mode 100644 Mage/src/main/java/mage/abilities/condition/common/RevealedOrControlledDragonCondition.java create mode 100644 Mage/src/main/java/mage/abilities/costs/common/RevealDragonFromHandCost.java diff --git a/Mage.Sets/src/mage/cards/d/DraconicRoar.java b/Mage.Sets/src/mage/cards/d/DraconicRoar.java index 2868e31525..d4eda4e9eb 100644 --- a/Mage.Sets/src/mage/cards/d/DraconicRoar.java +++ b/Mage.Sets/src/mage/cards/d/DraconicRoar.java @@ -1,23 +1,17 @@ package mage.cards.d; import mage.abilities.Ability; -import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.common.RevealTargetFromHandCost; +import mage.abilities.condition.common.RevealedOrControlledDragonCondition; +import mage.abilities.costs.common.RevealDragonFromHandCost; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.DamageTargetEffect; -import mage.abilities.effects.common.InfoEffect; 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.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; -import mage.target.common.TargetCardInHand; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.common.DragonOnTheBattlefieldWhileSpellWasCastWatcher; import java.util.UUID; @@ -30,14 +24,11 @@ public final class DraconicRoar extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}"); // As an additional cost to cast Draconic Roar, you may reveal a Dragon card from your hand. - this.getSpellAbility().addEffect(new InfoEffect("as an additional cost to cast this spell, you may reveal a Dragon card from your hand")); - this.getSpellAbility().setCostAdjuster(DraconicRoarAdjuster.instance); + this.getSpellAbility().addCost(new RevealDragonFromHandCost()); // Draconic Roar deals 3 damage to target creature. If you revealed a Dragon card or controlled a Dragon as you cast Draconic Roar, Draconic Roar deals 3 damage to that creature's controller. - this.getSpellAbility().addEffect(new DamageTargetEffect(3)); - this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addEffect(new DraconicRoarEffect()); - this.getSpellAbility().addWatcher(new DragonOnTheBattlefieldWhileSpellWasCastWatcher()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } private DraconicRoar(final DraconicRoar card) { @@ -50,34 +41,15 @@ public final class DraconicRoar extends CardImpl { } } -enum DraconicRoarAdjuster implements CostAdjuster { - instance; - - private static final FilterCard filter = new FilterCard("a Dragon card from your hand (you don't have to)"); - - static { - filter.add(SubType.DRAGON.getPredicate()); - } - - @Override - public void adjustCosts(Ability ability, Game game) { - Player controller = game.getPlayer(ability.getControllerId()); - if (controller != null) { - if (controller.getHand().count(filter, game) > 0) { - ability.addCost(new RevealTargetFromHandCost(new TargetCardInHand(0, 1, filter))); - } - } - } -} - class DraconicRoarEffect extends OneShotEffect { - public DraconicRoarEffect() { + DraconicRoarEffect() { super(Outcome.Benefit); - this.staticText = "If you revealed a Dragon card or controlled a Dragon as you cast {this}, {this} deals 3 damage to that creature's controller"; + staticText = "{this} deals 3 damage to target creature. If you revealed a Dragon card or controlled " + + "a Dragon as you cast this spell, {this} deals 3 damage to that creature’s controller."; } - public DraconicRoarEffect(final DraconicRoarEffect effect) { + private DraconicRoarEffect(final DraconicRoarEffect effect) { super(effect); } @@ -88,21 +60,18 @@ class DraconicRoarEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - DragonOnTheBattlefieldWhileSpellWasCastWatcher watcher = game.getState().getWatcher(DragonOnTheBattlefieldWhileSpellWasCastWatcher.class); - if (watcher != null && watcher.castWithConditionTrue(source.getId())) { - Permanent permanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); - if (permanent != null) { - Player player = game.getPlayer(permanent.getControllerId()); - if (player != null) { - player.damage(3, source.getSourceId(), source, game); - } - } - } + Permanent permanent = game.getPermanent(source.getFirstTarget()); + if (permanent == null) { + return false; + } + permanent.damage(3, source.getSourceId(), source, game); + if (!RevealedOrControlledDragonCondition.instance.apply(game, source)) { return true; } - return false; + Player player = game.getPlayer(permanent.getControllerId()); + if (player != null) { + player.damage(3, source.getSourceId(), source, game); + } + return true; } } - diff --git a/Mage.Sets/src/mage/cards/d/DragonlordsPrerogative.java b/Mage.Sets/src/mage/cards/d/DragonlordsPrerogative.java index 7f5685292e..45bbca867a 100644 --- a/Mage.Sets/src/mage/cards/d/DragonlordsPrerogative.java +++ b/Mage.Sets/src/mage/cards/d/DragonlordsPrerogative.java @@ -1,28 +1,15 @@ - package mage.cards.d; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.Condition; -import mage.abilities.costs.Cost; -import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.common.RevealTargetFromHandCost; -import mage.abilities.decorator.ConditionalContinuousRuleModifyingEffect; -import mage.abilities.effects.ContinuousRuleModifyingEffect; +import mage.abilities.condition.common.RevealedOrControlledDragonCondition; +import mage.abilities.costs.common.RevealDragonFromHandCost; +import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.CantBeCounteredSourceEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; -import mage.abilities.effects.common.InfoEffect; 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.common.FilterControlledPermanent; -import mage.game.Game; -import mage.game.stack.Spell; -import mage.players.Player; -import mage.target.common.TargetCardInHand; import java.util.UUID; @@ -35,20 +22,19 @@ public final class DragonlordsPrerogative extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{U}{U}"); // As an additional cost to cast Dragonlord's Prerogative, you may reveal a Dragon card from your hand. - this.getSpellAbility().addEffect(new InfoEffect("as an additional cost to cast this spell, you may reveal a Dragon card from your hand")); - this.getSpellAbility().setCostAdjuster(DragonlordsPrerogativeAdjuster.instance); + this.getSpellAbility().addCost(new RevealDragonFromHandCost()); // If you revealed a Dragon card or controlled a Dragon as you cast Dragonlord's Prerogative, Dragonlord's Prerogative can't be countered. - Condition condition = new DragonlordsPrerogativeCondition(); - ContinuousRuleModifyingEffect cantBeCountered = new CantBeCounteredSourceEffect(); - ConditionalContinuousRuleModifyingEffect conditionalCantBeCountered = new ConditionalContinuousRuleModifyingEffect(cantBeCountered, condition); - conditionalCantBeCountered.setText("
If you revealed a Dragon card or controlled a Dragon as you cast {this}, this spell can't be countered"); - Ability ability = new SimpleStaticAbility(Zone.STACK, conditionalCantBeCountered); - this.addAbility(ability); + this.addAbility(new SimpleStaticAbility( + Zone.STACK, + new ConditionalContinuousEffect( + new CantBeCounteredSourceEffect(), RevealedOrControlledDragonCondition.instance, + "if you revealed a Dragon card or controlled a Dragon as you cast this spell, this spell can't be countered" + ) + )); // Draw four cards. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(4)); - } private DragonlordsPrerogative(final DragonlordsPrerogative card) { @@ -60,49 +46,3 @@ public final class DragonlordsPrerogative extends CardImpl { return new DragonlordsPrerogative(this); } } - -enum DragonlordsPrerogativeAdjuster implements CostAdjuster { - instance; - private static final FilterCard filter = new FilterCard("a Dragon card from your hand"); - - static { - filter.add(SubType.DRAGON.getPredicate()); - } - - @Override - public void adjustCosts(Ability ability, Game game) { - Player controller = game.getPlayer(ability.getControllerId()); - if (controller != null) { - if (controller.getHand().count(filter, game) > 0) { - ability.addCost(new RevealTargetFromHandCost(new TargetCardInHand(0, 1, filter))); - } - } - } -} - -class DragonlordsPrerogativeCondition implements Condition { - - private static final FilterControlledPermanent filter = new FilterControlledPermanent("Dragon"); - - static { - filter.add(SubType.DRAGON.getPredicate()); - } - - @Override - public boolean apply(Game game, Ability source) { - boolean applies = false; - Spell spell = game.getStack().getSpell(source.getSourceId()); - if (spell != null && spell.getSpellAbility() != null) { - for (Cost cost : spell.getSpellAbility().getCosts()) { - if (cost instanceof RevealTargetFromHandCost) { - applies = !cost.getTargets().isEmpty(); - break; - } - } - } - if (!applies) { - applies = game.getBattlefield().countAll(filter, source.getControllerId(), game) > 0; - } - return applies; - } -} diff --git a/Mage.Sets/src/mage/cards/f/FoulTongueInvocation.java b/Mage.Sets/src/mage/cards/f/FoulTongueInvocation.java index ae3abe2fbe..8f5a396031 100644 --- a/Mage.Sets/src/mage/cards/f/FoulTongueInvocation.java +++ b/Mage.Sets/src/mage/cards/f/FoulTongueInvocation.java @@ -1,24 +1,15 @@ - package mage.cards.f; -import mage.abilities.Ability; -import mage.abilities.costs.CostAdjuster; -import mage.abilities.costs.common.RevealTargetFromHandCost; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.InfoEffect; +import mage.abilities.condition.common.RevealedOrControlledDragonCondition; +import mage.abilities.costs.common.RevealDragonFromHandCost; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.SacrificeEffect; 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.StaticFilters; -import mage.game.Game; -import mage.players.Player; import mage.target.TargetPlayer; -import mage.target.common.TargetCardInHand; -import mage.watchers.common.DragonOnTheBattlefieldWhileSpellWasCastWatcher; import java.util.UUID; @@ -31,14 +22,17 @@ public final class FoulTongueInvocation extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); // As an additional cost to cast Foul-Tongue Invocation, you may reveal a Dragon card from your hand. - this.getSpellAbility().addEffect(new InfoEffect("as an additional cost to cast this spell, you may reveal a Dragon card from your hand")); - this.getSpellAbility().setCostAdjuster(FoulTongueInvocationAdjuster.instance); + this.getSpellAbility().addCost(new RevealDragonFromHandCost()); // Target player sacrifices a creature. If you revealed a Dragon card or controlled a Dragon as you cast Foul-Tongue Invocation, you gain 4 life. + this.getSpellAbility().addEffect(new SacrificeEffect( + StaticFilters.FILTER_PERMANENT_CREATURE, 1, "target player" + )); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new GainLifeEffect(4), RevealedOrControlledDragonCondition.instance, + "If you revealed a Dragon card or controlled a Dragon as you cast this spell, you gain 4 life." + )); this.getSpellAbility().addTarget(new TargetPlayer()); - this.getSpellAbility().addEffect(new SacrificeEffect(StaticFilters.FILTER_PERMANENT_CREATURE, 1, "target player")); - this.getSpellAbility().addEffect(new FoulTongueInvocationEffect()); - this.getSpellAbility().addWatcher(new DragonOnTheBattlefieldWhileSpellWasCastWatcher()); } private FoulTongueInvocation(final FoulTongueInvocation card) { @@ -50,52 +44,3 @@ public final class FoulTongueInvocation extends CardImpl { return new FoulTongueInvocation(this); } } - -enum FoulTongueInvocationAdjuster implements CostAdjuster { - instance; - private static final FilterCard filter = new FilterCard("a Dragon card from your hand (you don't have to)"); - - static { - filter.add(SubType.DRAGON.getPredicate()); - } - - @Override - public void adjustCosts(Ability ability, Game game) { - Player controller = game.getPlayer(ability.getControllerId()); - if (controller != null) { - if (controller.getHand().count(filter, game) > 0) { - ability.addCost(new RevealTargetFromHandCost(new TargetCardInHand(0, 1, filter))); - } - } - } -} - -class FoulTongueInvocationEffect extends OneShotEffect { - - public FoulTongueInvocationEffect() { - super(Outcome.Benefit); - this.staticText = "If you revealed a Dragon card or controlled a Dragon as you cast {this}, you gain 4 life"; - } - - public FoulTongueInvocationEffect(final FoulTongueInvocationEffect effect) { - super(effect); - } - - @Override - public FoulTongueInvocationEffect copy() { - return new FoulTongueInvocationEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - DragonOnTheBattlefieldWhileSpellWasCastWatcher watcher = game.getState().getWatcher(DragonOnTheBattlefieldWhileSpellWasCastWatcher.class); - if (watcher != null && watcher.castWithConditionTrue(source.getId())) { - controller.gainLife(4, game, source); - } - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/o/OratorOfOjutai.java b/Mage.Sets/src/mage/cards/o/OratorOfOjutai.java index 4e511f34ce..a2bf9f2314 100644 --- a/Mage.Sets/src/mage/cards/o/OratorOfOjutai.java +++ b/Mage.Sets/src/mage/cards/o/OratorOfOjutai.java @@ -1,40 +1,26 @@ - package mage.cards.o; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.common.RevealTargetFromHandCost; -import mage.abilities.effects.Effect; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.InfoEffect; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.RevealedOrControlledDragonCondition; +import mage.abilities.costs.common.RevealDragonFromHandCost; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.keyword.DefenderAbility; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; -import mage.filter.FilterCard; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.common.TargetCardInHand; +import mage.constants.CardType; +import mage.constants.SubType; import mage.watchers.common.DragonOnTheBattlefieldWhileSpellWasCastWatcher; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class OratorOfOjutai extends CardImpl { - private static final FilterCard filter = new FilterCard("a Dragon card from your hand (you don't have to)"); - - static { - filter.add(SubType.DRAGON.getPredicate()); - } - public OratorOfOjutai(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); this.subtype.add(SubType.BIRD); @@ -47,22 +33,14 @@ public final class OratorOfOjutai extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // As an additional cost to cast Orator of Ojutai, you may reveal a Dragon card from your hand. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect("as an additional cost to cast this spell, you may reveal a Dragon card from your hand"))); + this.getSpellAbility().addCost(new RevealDragonFromHandCost()); // When Orator of Ojutai enters the battlefield, if you revealed a Dragon card or controlled a Dragon as you cast Orator of Ojutai, draw a card. - this.addAbility(new OratorOfOjutaiTriggeredAbility(new OratorOfOjutaiEffect()), new DragonOnTheBattlefieldWhileSpellWasCastWatcher()); - } - - @Override - public void adjustCosts(Ability ability, Game game) { - if (ability.getAbilityType() == AbilityType.SPELL) { - Player controller = game.getPlayer(ability.getControllerId()); - if (controller != null) { - if (controller.getHand().count(filter, game) > 0) { - ability.addCost(new RevealTargetFromHandCost(new TargetCardInHand(0, 1, filter))); - } - } - } + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1)), + RevealedOrControlledDragonCondition.instance, "When {this} enters the battlefield, " + + "if you revealed a Dragon card or controlled a Dragon as you cast this spell, draw a card." + ), new DragonOnTheBattlefieldWhileSpellWasCastWatcher()); } private OratorOfOjutai(final OratorOfOjutai card) { @@ -74,73 +52,3 @@ public final class OratorOfOjutai extends CardImpl { return new OratorOfOjutai(this); } } - -class OratorOfOjutaiTriggeredAbility extends TriggeredAbilityImpl { - - public OratorOfOjutaiTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect, false); - } - - public OratorOfOjutaiTriggeredAbility(final OratorOfOjutaiTriggeredAbility ability) { - super(ability); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - //Intervening if must be checked - Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(getSourceId()); - DragonOnTheBattlefieldWhileSpellWasCastWatcher watcher = game.getState().getWatcher(DragonOnTheBattlefieldWhileSpellWasCastWatcher.class); - return event.getTargetId().equals(getSourceId()) - && watcher != null - && watcher.castWithConditionTrue(sourcePermanent.getSpellAbility().getId()); - } - - @Override - public String getRule() { - return "When {this} enters the battlefield, " + super.getRule(); - } - - @Override - public OratorOfOjutaiTriggeredAbility copy() { - return new OratorOfOjutaiTriggeredAbility(this); - } -} - -class OratorOfOjutaiEffect extends OneShotEffect { - - public OratorOfOjutaiEffect() { - super(Outcome.Benefit); - this.staticText = "If you revealed a Dragon card or controlled a Dragon as you cast {this}, draw a card"; - } - - public OratorOfOjutaiEffect(final OratorOfOjutaiEffect effect) { - super(effect); - } - - @Override - public OratorOfOjutaiEffect copy() { - return new OratorOfOjutaiEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - //Intervening if is checked again on resolution - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); - if (sourcePermanent != null) { - DragonOnTheBattlefieldWhileSpellWasCastWatcher watcher = game.getState().getWatcher(DragonOnTheBattlefieldWhileSpellWasCastWatcher.class); - if (watcher != null && watcher.castWithConditionTrue(sourcePermanent.getSpellAbility().getId())) { - controller.drawCards(1, source, game); - return true; - } - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/s/ScaleguardSentinels.java b/Mage.Sets/src/mage/cards/s/ScaleguardSentinels.java index 53c976f313..710508cadd 100644 --- a/Mage.Sets/src/mage/cards/s/ScaleguardSentinels.java +++ b/Mage.Sets/src/mage/cards/s/ScaleguardSentinels.java @@ -1,39 +1,24 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; -import mage.abilities.condition.Condition; -import mage.abilities.costs.common.RevealTargetFromHandCost; -import mage.abilities.effects.common.InfoEffect; +import mage.abilities.condition.common.RevealedOrControlledDragonCondition; +import mage.abilities.costs.common.RevealDragonFromHandCost; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityType; import mage.constants.CardType; import mage.constants.SubType; import mage.counters.CounterType; -import mage.filter.FilterCard; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.common.TargetCardInHand; import mage.watchers.common.DragonOnTheBattlefieldWhileSpellWasCastWatcher; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class ScaleguardSentinels extends CardImpl { - private static final FilterCard filter = new FilterCard("a Dragon card from your hand (you don't have to)"); - - static { - filter.add(SubType.DRAGON.getPredicate()); - } - public ScaleguardSentinels(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{G}"); this.subtype.add(SubType.HUMAN); @@ -42,26 +27,16 @@ public final class ScaleguardSentinels extends CardImpl { this.toughness = new MageInt(3); // As an additional cost to cast Scaleguard Sentinels, you may reveal a Dragon card from your hand. - this.getSpellAbility().addEffect(new InfoEffect("as an additional cost to cast this spell, you may reveal a Dragon card from your hand")); + this.getSpellAbility().addCost(new RevealDragonFromHandCost()); // Scaleguard Sentinels enters the battlefield with a +1/+1 counter on it if you revealed a Dragon card or controlled a Dragon as you cast Scaleguard Sentinels. - this.addAbility(new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(), true), - ScaleguardSentinelsCondition.instance, - "{this} enters the battlefield with a +1/+1 counter on it if you revealed a Dragon card or controlled a Dragon as you cast {this}.", ""), - new DragonOnTheBattlefieldWhileSpellWasCastWatcher()); - - } - - @Override - public void adjustCosts(Ability ability, Game game) { - if (ability.getAbilityType() == AbilityType.SPELL) { - Player controller = game.getPlayer(ability.getControllerId()); - if (controller != null) { - if (controller.getHand().count(filter, game) > 0) { - ability.addCost(new RevealTargetFromHandCost(new TargetCardInHand(0, 1, filter))); - } - } - } + this.addAbility(new EntersBattlefieldAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), + RevealedOrControlledDragonCondition.instance, "{this} enters the battlefield " + + "with a +1/+1 counter on it if you revealed a Dragon card " + + "or controlled a Dragon as you cast this spell.", "" + ), new DragonOnTheBattlefieldWhileSpellWasCastWatcher() + ); } private ScaleguardSentinels(final ScaleguardSentinels card) { @@ -73,18 +48,3 @@ public final class ScaleguardSentinels extends CardImpl { return new ScaleguardSentinels(this); } } - -enum ScaleguardSentinelsCondition implements Condition { - - instance; - - @Override - public boolean apply(Game game, Ability source) { - Permanent sourcePermanent = game.getPermanentEntering(source.getSourceId()); - if (sourcePermanent != null) { - DragonOnTheBattlefieldWhileSpellWasCastWatcher watcher = game.getState().getWatcher(DragonOnTheBattlefieldWhileSpellWasCastWatcher.class); - return (watcher != null && watcher.castWithConditionTrue(sourcePermanent.getSpellAbility().getId())); - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/s/SilumgarsScorn.java b/Mage.Sets/src/mage/cards/s/SilumgarsScorn.java index 93779841e0..d922fd4596 100644 --- a/Mage.Sets/src/mage/cards/s/SilumgarsScorn.java +++ b/Mage.Sets/src/mage/cards/s/SilumgarsScorn.java @@ -1,65 +1,38 @@ - package mage.cards.s; -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.RevealTargetFromHandCost; +import mage.abilities.condition.common.RevealedOrControlledDragonCondition; +import mage.abilities.costs.common.RevealDragonFromHandCost; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.InfoEffect; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.CounterUnlessPaysEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.AbilityType; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.filter.FilterCard; -import mage.game.Game; -import mage.game.stack.StackObject; -import mage.players.Player; import mage.target.TargetSpell; -import mage.target.common.TargetCardInHand; -import mage.watchers.common.DragonOnTheBattlefieldWhileSpellWasCastWatcher; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class SilumgarsScorn extends CardImpl { - private static final FilterCard filter = new FilterCard("a Dragon card from your hand (you don't have to)"); - - static { - filter.add(SubType.DRAGON.getPredicate()); - } - public SilumgarsScorn(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{U}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{U}{U}"); // As an additional cost to cast Silumgar's Scorn, you may reveal a Dragon card from your hand. - this.getSpellAbility().addEffect(new InfoEffect("as an additional cost to cast this spell, you may reveal a Dragon card from your hand")); - + this.getSpellAbility().addCost(new RevealDragonFromHandCost()); + // Counter target spell unless its controller pays {1}. If you revealed a Dragon card or controlled a Dragon as you cast Silumgar's Scorn, counter that spell instead. - this.getSpellAbility().addEffect(new SilumgarsScornCounterEffect()); + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new CounterTargetEffect(), new CounterUnlessPaysEffect(new GenericManaCost(1)), + RevealedOrControlledDragonCondition.instance, "counter target spell unless its controller pays {1}. " + + "If you revealed a Dragon card or controlled a Dragon as you cast this spell, counter that spell instead" + )); this.getSpellAbility().addTarget(new TargetSpell()); - this.getSpellAbility().addWatcher(new DragonOnTheBattlefieldWhileSpellWasCastWatcher()); - } - - @Override - public void adjustCosts(Ability ability, Game game) { - if (ability.getAbilityType() == AbilityType.SPELL) { - Player controller = game.getPlayer(ability.getControllerId()); - if (controller != null) { - if (controller.getHand().count(filter, game) > 0) { - ability.addCost(new RevealTargetFromHandCost(new TargetCardInHand(0,1, filter))); - } - } - } - } - private SilumgarsScorn(final SilumgarsScorn card) { super(card); } @@ -69,48 +42,3 @@ public final class SilumgarsScorn extends CardImpl { return new SilumgarsScorn(this); } } - -class SilumgarsScornCounterEffect extends OneShotEffect { - - public SilumgarsScornCounterEffect() { - super(Outcome.Detriment); - staticText = "
Counter target spell unless its controller pays {1}. If you revealed a Dragon card or controlled a Dragon as you cast {this}, counter that spell instead"; - } - - public SilumgarsScornCounterEffect(final SilumgarsScornCounterEffect effect) { - super(effect); - } - - @Override - public SilumgarsScornCounterEffect copy() { - return new SilumgarsScornCounterEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source)); - if (spell != null) { - Player player = game.getPlayer(spell.getControllerId()); - if (player != null) { - DragonOnTheBattlefieldWhileSpellWasCastWatcher watcher = game.getState().getWatcher(DragonOnTheBattlefieldWhileSpellWasCastWatcher.class); - boolean condition = watcher != null && watcher.castWithConditionTrue(source.getId()); - if (!condition) { - for (Cost cost: source.getCosts()) { - if (cost instanceof RevealTargetFromHandCost) { - condition = ((RevealTargetFromHandCost)cost).getNumberRevealedCards() > 0; - } - } - } - if (condition) { - return game.getStack().counter(spell.getId(), source, game); - } - if (!(player.chooseUse(Outcome.Benefit, "Would you like to pay {1} to prevent counter effect?", source, game) && - new GenericManaCost(1).pay(source, game, source, spell.getControllerId(), false))) { - return game.getStack().counter(spell.getId(), source, game); - } - } - } - return true; - } - -} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RevealedOrControlledDragonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RevealedOrControlledDragonTest.java new file mode 100644 index 0000000000..04a5a806da --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/RevealedOrControlledDragonTest.java @@ -0,0 +1,181 @@ +package org.mage.test.cards.cost.additional; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class RevealedOrControlledDragonTest extends CardTestPlayerBase { + private static final String dragon = "Shivan Dragon"; + private static final String roar = "Draconic Roar"; + private static final String lion = "Silvercoat Lion"; + private static final String orator = "Orator of Ojutai"; + private static final String sentinels = "Scaleguard Sentinels"; + + public void addDragonToHand(String cardName) { + addCard(Zone.HAND, playerA, dragon); + addCard(Zone.HAND, playerA, cardName); + } + + public void addDragonToBattlefield(String cardName) { + addCard(Zone.BATTLEFIELD, playerA, dragon); + addCard(Zone.HAND, playerA, cardName); + } + + @Test + public void testRoarHand() { + addDragonToHand(roar); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, lion); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, roar, lion); + setChoice(playerA, dragon); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, lion, 1); + assertGraveyardCount(playerA, roar, 1); + assertLife(playerA, 20 - 3); + } + + @Test + public void testRoarBattlefield() { + addDragonToBattlefield(roar); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, lion); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, roar, lion); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, lion, 1); + assertGraveyardCount(playerA, roar, 1); + assertLife(playerA, 20 - 3); + } + + @Test + public void testRoarFalse() { + addCard(Zone.HAND, playerA, roar); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, lion); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, roar, lion); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertGraveyardCount(playerA, lion, 1); + assertGraveyardCount(playerA, roar, 1); + assertLife(playerA, 20); + } + + @Test + public void testOratorHand() { + addDragonToHand(orator); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, orator); + setChoice(playerA, dragon); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, orator, 1); + assertHandCount(playerA, 1 + 1); + } + + @Test + public void testOratorBattlefield() { + addDragonToBattlefield(orator); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, orator); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, orator, 1); + assertHandCount(playerA, 1); + } + + @Test + public void testOratorFalse() { + addCard(Zone.HAND, playerA, orator); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, orator); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, orator, 1); + assertHandCount(playerA, 0); + } + + @Test + public void testSentinelsHand() { + addDragonToHand(sentinels); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sentinels); + setChoice(playerA, dragon); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, sentinels, 1); + assertCounterCount(playerA, sentinels, CounterType.P1P1, 1); + } + + @Test + public void testSentinelsBattlefield() { + addDragonToBattlefield(sentinels); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sentinels); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, sentinels, 1); + assertCounterCount(playerA, sentinels, CounterType.P1P1, 1); + } + + @Test + public void testSentinelsFalse() { + addCard(Zone.HAND, playerA, sentinels); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, sentinels); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + + assertPermanentCount(playerA, sentinels, 1); + assertCounterCount(playerA, sentinels, CounterType.P1P1, 0); + } +} diff --git a/Mage/src/main/java/mage/abilities/condition/common/RevealedOrControlledDragonCondition.java b/Mage/src/main/java/mage/abilities/condition/common/RevealedOrControlledDragonCondition.java new file mode 100644 index 0000000000..8658e5897b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/RevealedOrControlledDragonCondition.java @@ -0,0 +1,30 @@ +package mage.abilities.condition.common; + +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.abilities.costs.common.RevealDragonFromHandCost; +import mage.constants.AbilityType; +import mage.game.Game; +import mage.watchers.common.DragonOnTheBattlefieldWhileSpellWasCastWatcher; + +/** + * @author TheElk801 + */ +public enum RevealedOrControlledDragonCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + if (source.getAbilityType() == AbilityType.SPELL) { + return source + .getCosts() + .stream() + .filter(RevealDragonFromHandCost.class::isInstance) + .map(RevealDragonFromHandCost.class::cast) + .anyMatch(RevealDragonFromHandCost::isRevealedOrControlled); + } + DragonOnTheBattlefieldWhileSpellWasCastWatcher watcher + = game.getState().getWatcher(DragonOnTheBattlefieldWhileSpellWasCastWatcher.class); + return watcher != null && watcher.checkCondition(source, game); + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/common/RevealDragonFromHandCost.java b/Mage/src/main/java/mage/abilities/costs/common/RevealDragonFromHandCost.java new file mode 100644 index 0000000000..26c0112f3c --- /dev/null +++ b/Mage/src/main/java/mage/abilities/costs/common/RevealDragonFromHandCost.java @@ -0,0 +1,58 @@ +package mage.abilities.costs.common; + +import mage.abilities.Ability; +import mage.abilities.costs.Cost; +import mage.constants.SubType; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.target.common.TargetCardInHand; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public class RevealDragonFromHandCost extends RevealTargetFromHandCost { + + private static final FilterCard filter = new FilterCard("a Dragon card from your hand"); + private static final FilterPermanent filter2 = new FilterControlledPermanent(SubType.DRAGON); + + static { + filter.add(SubType.DRAGON.getPredicate()); + } + + private boolean revealedOrControlled = false; + + public RevealDragonFromHandCost() { + super(new TargetCardInHand(0, 1, filter)); + } + + private RevealDragonFromHandCost(final RevealDragonFromHandCost cost) { + super(cost); + this.revealedOrControlled = cost.revealedOrControlled; + } + + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + return true; + } + + @Override + public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { + super.pay(ability, game, source, controllerId, noMana, costToPay); + revealedOrControlled = numberCardsRevealed > 0 + || game.getBattlefield().count(filter2, source.getSourceId(), controllerId, game) > 0; + return paid = true; + } + + @Override + public RevealDragonFromHandCost copy() { + return new RevealDragonFromHandCost(this); + } + + public boolean isRevealedOrControlled() { + return revealedOrControlled; + } +} diff --git a/Mage/src/main/java/mage/watchers/common/DragonOnTheBattlefieldWhileSpellWasCastWatcher.java b/Mage/src/main/java/mage/watchers/common/DragonOnTheBattlefieldWhileSpellWasCastWatcher.java index 0d5ee938bd..d33cdc0f1a 100644 --- a/Mage/src/main/java/mage/watchers/common/DragonOnTheBattlefieldWhileSpellWasCastWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/DragonOnTheBattlefieldWhileSpellWasCastWatcher.java @@ -1,10 +1,9 @@ package mage.watchers.common; -import mage.abilities.costs.Cost; -import mage.abilities.costs.common.RevealTargetFromHandCost; -import mage.constants.SubType; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.costs.common.RevealDragonFromHandCost; import mage.constants.WatcherScope; -import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; @@ -12,16 +11,13 @@ import mage.watchers.Watcher; import java.util.HashSet; import java.util.Set; -import java.util.UUID; /** * @author LevelX2 */ public class DragonOnTheBattlefieldWhileSpellWasCastWatcher extends Watcher { - private static final FilterPermanent filter = new FilterPermanent(SubType.DRAGON, "Dragons"); - - private final Set castWithDragonOnTheBattlefield = new HashSet<>(); + private final Set castWithDragonOnTheBattlefield = new HashSet<>(); public DragonOnTheBattlefieldWhileSpellWasCastWatcher() { super(WatcherScope.GAME); @@ -29,28 +25,22 @@ public class DragonOnTheBattlefieldWhileSpellWasCastWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.SPELL_CAST) { - // targetId is the unique ID of the spell - Spell spell = game.getState().getStack().getSpell(event.getTargetId()); - // revealed a Dragon card or controlled a Dragon as you cast the spell - if (spell != null) { - boolean revealedOrOnBattlefield = false; - if (spell.getSpellAbility() != null) { - for (Cost cost : spell.getSpellAbility().getCosts()) { - if (cost instanceof RevealTargetFromHandCost) { - revealedOrOnBattlefield = ((RevealTargetFromHandCost) cost).getNumberRevealedCards() > 0; - break; - } - } - } - if (!revealedOrOnBattlefield) { - revealedOrOnBattlefield = game.getBattlefield().countAll(filter, spell.getControllerId(), game) > 0; - } - if (revealedOrOnBattlefield) { - castWithDragonOnTheBattlefield.add(spell.getId()); - } - - } + if (event.getType() != GameEvent.EventType.SPELL_CAST) { + return; + } + // targetId is the unique ID of the spell + Spell spell = game.getSpell(event.getTargetId()); + // revealed a Dragon card or controlled a Dragon as you cast the spell + if (spell != null + && spell + .getSpellAbility() + .getCosts() + .stream() + .filter(RevealDragonFromHandCost.class::isInstance) + .map(RevealDragonFromHandCost.class::cast) + .anyMatch(RevealDragonFromHandCost::isRevealedOrControlled)) { + castWithDragonOnTheBattlefield.add(new MageObjectReference(spell.getCard(), game, 0)); + castWithDragonOnTheBattlefield.add(new MageObjectReference(spell.getCard(), game, 1)); } } @@ -60,7 +50,9 @@ public class DragonOnTheBattlefieldWhileSpellWasCastWatcher extends Watcher { castWithDragonOnTheBattlefield.clear(); } - public boolean castWithConditionTrue(UUID spellId) { - return castWithDragonOnTheBattlefield.contains(spellId); + public boolean checkCondition(Ability source, Game game) { + return castWithDragonOnTheBattlefield + .stream() + .anyMatch(mor -> mor.refersTo(source.getSourceObject(game), game)); } }