diff --git a/Mage.Sets/src/mage/cards/a/ArcadeGannon.java b/Mage.Sets/src/mage/cards/a/ArcadeGannon.java index f70ee03061b..28400d471cb 100644 --- a/Mage.Sets/src/mage/cards/a/ArcadeGannon.java +++ b/Mage.Sets/src/mage/cards/a/ArcadeGannon.java @@ -2,7 +2,7 @@ package mage.cards.a; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DrawDiscardControllerEffect; @@ -24,14 +24,13 @@ import mage.game.permanent.Permanent; import java.util.UUID; /** - * * @author justinjohnson14 */ public final class ArcadeGannon extends CardImpl { private static final FilterCard filter = new FilterCard("an artifact or Human spell from your graveyard with mana value less than or equal to the number of quest counters on {this}"); - static{ + static { filter.add(Predicates.or( CardType.ARTIFACT.getPredicate(), SubType.HUMAN.getPredicate() @@ -41,7 +40,7 @@ public final class ArcadeGannon extends CardImpl { public ArcadeGannon(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{U}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.DOCTOR); @@ -49,12 +48,12 @@ public final class ArcadeGannon extends CardImpl { this.toughness = new MageInt(3); // {T}: Draw a card, then discard a card. Put a quest counter on Arcade Gannon. - Ability ability = (new SimpleActivatedAbility(new DrawDiscardControllerEffect(1,1), new TapSourceCost())); + Ability ability = (new SimpleActivatedAbility(new DrawDiscardControllerEffect(1, 1), new TapSourceCost())); ability.addEffect(new AddCountersSourceEffect(CounterType.QUEST.createInstance(1))); this.addAbility(ability); // For Auld Lang Syne -- Once during each of your turns, you may cast an artifact or Human spell from your graveyard with mana value less than or equal to the number of quest counters on Arcade Gannon. - this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter).withFlavorWord("For Auld Lang Syne")); + this.addAbility(new CastFromGraveyardOnceDuringEachOfYourTurnAbility(filter).withFlavorWord("For Auld Lang Syne")); } private ArcadeGannon(final ArcadeGannon card) { diff --git a/Mage.Sets/src/mage/cards/b/BanonTheReturnersLeader.java b/Mage.Sets/src/mage/cards/b/BanonTheReturnersLeader.java index e078c590826..81f8236d50e 100644 --- a/Mage.Sets/src/mage/cards/b/BanonTheReturnersLeader.java +++ b/Mage.Sets/src/mage/cards/b/BanonTheReturnersLeader.java @@ -1,26 +1,27 @@ package mage.cards.b; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; -import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; import mage.abilities.costs.CompositeCost; import mage.abilities.costs.common.DiscardCardCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.Card; -import mage.constants.SubType; -import mage.constants.SuperType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.Predicate; import mage.game.Game; import mage.watchers.common.CardsPutIntoGraveyardWatcher; +import java.util.UUID; + /** * @author balazskristof */ @@ -36,7 +37,7 @@ public final class BanonTheReturnersLeader extends CardImpl { public BanonTheReturnersLeader(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.CLERIC); @@ -45,7 +46,7 @@ public final class BanonTheReturnersLeader extends CardImpl { this.toughness = new MageInt(3); // Pray -- Once during each of your turns, you may cast a creature spell from among cards in your graveyard that were put there from anywhere other than the battlefield this turn. - Ability ability = new CastFromGraveyardOnceEachTurnAbility(filter).withFlavorWord("Pray"); + Ability ability = new CastFromGraveyardOnceDuringEachOfYourTurnAbility(filter).withFlavorWord("Pray"); ability.addWatcher(new CardsPutIntoGraveyardWatcher()); this.addAbility(ability); // Whenever you attack, you may pay {1} and discard a card. If you do, draw a card. diff --git a/Mage.Sets/src/mage/cards/c/CommunalBrewing.java b/Mage.Sets/src/mage/cards/c/CommunalBrewing.java index 1ea3d970642..506ad83fdf7 100644 --- a/Mage.Sets/src/mage/cards/c/CommunalBrewing.java +++ b/Mage.Sets/src/mage/cards/c/CommunalBrewing.java @@ -12,7 +12,7 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SetTargetPointer; import mage.counters.CounterType; -import mage.filter.common.FilterCreatureSpell; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; import mage.game.events.GameEvent; @@ -28,8 +28,6 @@ import java.util.UUID; */ public final class CommunalBrewing extends CardImpl { - private static final FilterCreatureSpell filter = new FilterCreatureSpell("a creature spell"); - public CommunalBrewing(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); @@ -41,7 +39,11 @@ public final class CommunalBrewing extends CardImpl { // Whenever you cast a creature spell, that creature enters with X additional +1/+1 // counters on it, where X is the number of ingredient counters on Communal Brewing. - this.addAbility(new SpellCastControllerTriggeredAbility(new CommunalBrewingCountersEffect(), filter, false, SetTargetPointer.SPELL)); + this.addAbility(new SpellCastControllerTriggeredAbility( + new CommunalBrewingCountersEffect(), + StaticFilters.FILTER_SPELL_A_CREATURE, + false, SetTargetPointer.SPELL + )); } private CommunalBrewing(final CommunalBrewing card) { diff --git a/Mage.Sets/src/mage/cards/d/DanithaNewBenaliasLight.java b/Mage.Sets/src/mage/cards/d/DanithaNewBenaliasLight.java index b458bd99339..08806f3f1d5 100644 --- a/Mage.Sets/src/mage/cards/d/DanithaNewBenaliasLight.java +++ b/Mage.Sets/src/mage/cards/d/DanithaNewBenaliasLight.java @@ -1,7 +1,7 @@ package mage.cards.d; import mage.MageInt; -import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; import mage.abilities.keyword.LifelinkAbility; import mage.abilities.keyword.TrampleAbility; import mage.abilities.keyword.VigilanceAbility; @@ -21,6 +21,7 @@ import java.util.UUID; public final class DanithaNewBenaliasLight extends CardImpl { private static final FilterCard filter = new FilterCard("an Aura or Equipment spell"); + static { filter.add(Predicates.or( SubType.AURA.getPredicate(), SubType.EQUIPMENT.getPredicate() @@ -46,7 +47,7 @@ public final class DanithaNewBenaliasLight extends CardImpl { this.addAbility(LifelinkAbility.getInstance()); // Once during each of your turns, you may cast an Aura or Equipment spell from your graveyard. - this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter)); + this.addAbility(new CastFromGraveyardOnceDuringEachOfYourTurnAbility(filter)); } private DanithaNewBenaliasLight(final DanithaNewBenaliasLight card) { diff --git a/Mage.Sets/src/mage/cards/e/EdgarMasterMachinist.java b/Mage.Sets/src/mage/cards/e/EdgarMasterMachinist.java new file mode 100644 index 00000000000..5ff0dcc9046 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/EdgarMasterMachinist.java @@ -0,0 +1,55 @@ +package mage.cards.e; + +import mage.MageIdentifier; +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; +import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; +import mage.filter.common.FilterArtifactCard; + +import java.util.UUID; + +/** + * @author Susucr + */ +public final class EdgarMasterMachinist extends CardImpl { + + private static final FilterCard filter = new FilterArtifactCard("an artifact spell"); + + public EdgarMasterMachinist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ARTIFICER); + this.subtype.add(SubType.NOBLE); + this.power = new MageInt(2); + this.toughness = new MageInt(4); + + // Once during each of your turns, you may cast an artifact spell from your graveyard. If you cast a spell this way, that artifact enters tapped. + this.addAbility(new CastFromGraveyardOnceDuringEachOfYourTurnAbility(filter, MageIdentifier.OnceOnYourTurnCastFromGraveyardEntersTapped)); + + // Tools -- Whenever Edgar attacks, it gets +X/+0 until end of turn, where X is the greatest mana value among artifacts you control. + this.addAbility(new AttacksTriggeredAbility( + new BoostSourceEffect(GreatestAmongPermanentsValue.MANAVALUE_CONTROLLED_ARTIFACTS, StaticValue.get(0), Duration.EndOfTurn, "it") + ).withFlavorWord("Tools").addHint(GreatestAmongPermanentsValue.MANAVALUE_CONTROLLED_ARTIFACTS.getHint())); + } + + private EdgarMasterMachinist(final EdgarMasterMachinist card) { + super(card); + } + + @Override + public EdgarMasterMachinist copy() { + return new EdgarMasterMachinist(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/g/GisaAndGeralf.java b/Mage.Sets/src/mage/cards/g/GisaAndGeralf.java index b3520a300a4..a059536425a 100644 --- a/Mage.Sets/src/mage/cards/g/GisaAndGeralf.java +++ b/Mage.Sets/src/mage/cards/g/GisaAndGeralf.java @@ -1,7 +1,7 @@ package mage.cards.g; import mage.MageInt; -import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.MillCardsControllerEffect; import mage.cards.CardImpl; @@ -9,17 +9,18 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; +import mage.filter.FilterCard; import mage.filter.common.FilterCreatureCard; import java.util.UUID; /** - * * @author fireshoes */ public final class GisaAndGeralf extends CardImpl { - private static final FilterCreatureCard filter = new FilterCreatureCard("a Zombie creature spell"); + private static final FilterCard filter = new FilterCreatureCard("a Zombie creature spell"); + static { filter.add(SubType.ZOMBIE.getPredicate()); } @@ -36,7 +37,7 @@ public final class GisaAndGeralf extends CardImpl { this.addAbility(new EntersBattlefieldTriggeredAbility(new MillCardsControllerEffect(4))); // Once during each of your turns, you may cast a Zombie creature spell from your graveyard - this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter)); + this.addAbility(new CastFromGraveyardOnceDuringEachOfYourTurnAbility(filter)); } private GisaAndGeralf(final GisaAndGeralf card) { diff --git a/Mage.Sets/src/mage/cards/h/HazoretsMonument.java b/Mage.Sets/src/mage/cards/h/HazoretsMonument.java index 049ea10a9f5..5da178ce98a 100644 --- a/Mage.Sets/src/mage/cards/h/HazoretsMonument.java +++ b/Mage.Sets/src/mage/cards/h/HazoretsMonument.java @@ -1,7 +1,6 @@ package mage.cards.h; -import java.util.UUID; import mage.ObjectColor; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; @@ -13,27 +12,23 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SuperType; -import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.FilterSpell; +import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; +import java.util.UUID; + /** - * * @author fireshoes */ public final class HazoretsMonument extends CardImpl { private static final FilterCard filter = new FilterCard("Red creature spells"); - private static final FilterSpell filter2 = new FilterSpell("a creature spell"); static { filter.add(Predicates.and(new ColorPredicate(ObjectColor.RED), CardType.CREATURE.getPredicate())); } - static { - filter2.add(CardType.CREATURE.getPredicate()); - } public HazoretsMonument(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); @@ -44,7 +39,10 @@ public final class HazoretsMonument extends CardImpl { this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); // Whenever you cast a creature spell, you may discard a card. If you do, draw a card. - this.addAbility(new SpellCastControllerTriggeredAbility(new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new DiscardCardCost()), filter2, false)); + this.addAbility(new SpellCastControllerTriggeredAbility( + new DoIfCostPaid(new DrawCardSourceControllerEffect(1), new DiscardCardCost()), + StaticFilters.FILTER_SPELL_A_CREATURE, false + )); } private HazoretsMonument(final HazoretsMonument card) { diff --git a/Mage.Sets/src/mage/cards/k/KaradorGhostChieftain.java b/Mage.Sets/src/mage/cards/k/KaradorGhostChieftain.java index 37455d3b7ab..470449a0195 100644 --- a/Mage.Sets/src/mage/cards/k/KaradorGhostChieftain.java +++ b/Mage.Sets/src/mage/cards/k/KaradorGhostChieftain.java @@ -3,14 +3,13 @@ package mage.cards.k; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.SpellAbility; -import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreatureCard; import mage.game.Game; import mage.players.Player; import mage.util.CardUtil; @@ -18,13 +17,10 @@ import mage.util.CardUtil; import java.util.UUID; /** - * * @author emerald000 */ public final class KaradorGhostChieftain extends CardImpl { - private static final FilterCreatureCard filter = new FilterCreatureCard("a creature spell"); - public KaradorGhostChieftain(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}{B}{G}"); this.supertype.add(SuperType.LEGENDARY); @@ -39,7 +35,7 @@ public final class KaradorGhostChieftain extends CardImpl { new KaradorGhostChieftainCostReductionEffect())); // Once during each of your turns, you may cast a creature spell from your graveyard. - this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter)); + this.addAbility(new CastFromGraveyardOnceDuringEachOfYourTurnAbility(StaticFilters.FILTER_CARD_A_CREATURE_SPELL)); } private KaradorGhostChieftain(final KaradorGhostChieftain card) { diff --git a/Mage.Sets/src/mage/cards/k/KefnetsMonument.java b/Mage.Sets/src/mage/cards/k/KefnetsMonument.java index 1350482e94f..87763e026e9 100644 --- a/Mage.Sets/src/mage/cards/k/KefnetsMonument.java +++ b/Mage.Sets/src/mage/cards/k/KefnetsMonument.java @@ -1,7 +1,6 @@ package mage.cards.k; -import java.util.UUID; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -12,26 +11,23 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SuperType; -import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.FilterSpell; import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author fireshoes */ public final class KefnetsMonument extends CardImpl { private static final FilterCard filter = new FilterCard("Blue creature spells"); - private static final FilterSpell filter2 = new FilterSpell("a creature spell"); static { filter.add(Predicates.and(new ColorPredicate(ObjectColor.BLUE), CardType.CREATURE.getPredicate())); - filter2.add(CardType.CREATURE.getPredicate()); } public KefnetsMonument(UUID ownerId, CardSetInfo setInfo) { @@ -43,7 +39,10 @@ public final class KefnetsMonument extends CardImpl { this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); // Whenever you cast a creature spell, target creature an opponent controls doesn't untap during its controller's next untap step. - Ability ability = new SpellCastControllerTriggeredAbility(new DontUntapInControllersNextUntapStepTargetEffect(), filter2, false); + Ability ability = new SpellCastControllerTriggeredAbility( + new DontUntapInControllersNextUntapStepTargetEffect(), + StaticFilters.FILTER_SPELL_A_CREATURE, false + ); ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/k/KotisSibsigChampion.java b/Mage.Sets/src/mage/cards/k/KotisSibsigChampion.java index 2a5ea82c714..49fc09d007a 100644 --- a/Mage.Sets/src/mage/cards/k/KotisSibsigChampion.java +++ b/Mage.Sets/src/mage/cards/k/KotisSibsigChampion.java @@ -1,7 +1,7 @@ package mage.cards.k; import mage.MageInt; -import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; import mage.abilities.common.EntersBattlefieldOneOrMoreTriggeredAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.common.ExileFromGraveCost; @@ -12,7 +12,6 @@ import mage.constants.*; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.game.events.ZoneChangeEvent; @@ -23,21 +22,19 @@ import mage.target.common.TargetCardInYourGraveyard; import java.util.UUID; /** - * * @author Jmlundeen */ public final class KotisSibsigChampion extends CardImpl { - private static final FilterCreatureCard filter = new FilterCreatureCard("a creature spell"); - private static final FilterCard filter2 = new FilterCard("other cards"); + private static final FilterCard filter = new FilterCard("other cards"); static { - filter2.add(AnotherPredicate.instance); + filter.add(AnotherPredicate.instance); } public KotisSibsigChampion(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{B}{G}{U}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.ZOMBIE); this.subtype.add(SubType.WARRIOR); @@ -45,9 +42,9 @@ public final class KotisSibsigChampion extends CardImpl { this.toughness = new MageInt(3); // Once during each of your turns, you may cast a creature spell from your graveyard by exiling three other cards from your graveyard in addition to paying its other costs. - Cost cost = new ExileFromGraveCost(new TargetCardInYourGraveyard(3, filter2)); + Cost cost = new ExileFromGraveCost(new TargetCardInYourGraveyard(3, filter)); cost.setText(cost.getText().replace("exile", "exiling")); - this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter, cost)); + this.addAbility(new CastFromGraveyardOnceDuringEachOfYourTurnAbility(StaticFilters.FILTER_CARD_A_CREATURE_SPELL, cost)); // Whenever one or more creatures you control enter, if one or more of them entered from a graveyard or was cast from a graveyard, put two +1/+1 counters on Kotis. this.addAbility(new KotisSibsigTriggeredAbility()); diff --git a/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java b/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java index dece80f0725..bf7372a09d2 100644 --- a/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java +++ b/Mage.Sets/src/mage/cards/l/LurrusOfTheDreamDen.java @@ -2,7 +2,7 @@ package mage.cards.l; import mage.MageInt; import mage.MageObject; -import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; import mage.abilities.keyword.CompanionAbility; import mage.abilities.keyword.CompanionCondition; import mage.abilities.keyword.LifelinkAbility; @@ -13,8 +13,10 @@ import mage.constants.CardType; import mage.constants.ComparisonType; import mage.constants.SubType; import mage.constants.SuperType; +import mage.filter.FilterCard; import mage.filter.common.FilterPermanentCard; import mage.filter.predicate.mageobject.ManaValuePredicate; +import mage.filter.predicate.mageobject.PermanentPredicate; import java.util.Set; import java.util.UUID; @@ -24,9 +26,10 @@ import java.util.UUID; */ public final class LurrusOfTheDreamDen extends CardImpl { - private static final FilterPermanentCard filter = new FilterPermanentCard("a permanent spell with mana value 2 or less"); + private static final FilterCard filter = new FilterPermanentCard("a permanent spell with mana value 2 or less"); static { + filter.add(PermanentPredicate.instance); filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 3)); } @@ -46,7 +49,7 @@ public final class LurrusOfTheDreamDen extends CardImpl { this.addAbility(LifelinkAbility.getInstance()); // During each of your turns, you may cast one permanent spell with converted mana cost 2 or less from your graveyard. - this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter)); + this.addAbility(new CastFromGraveyardOnceDuringEachOfYourTurnAbility(filter)); } private LurrusOfTheDreamDen(final LurrusOfTheDreamDen card) { diff --git a/Mage.Sets/src/mage/cards/m/MendicantCoreGuidelight.java b/Mage.Sets/src/mage/cards/m/MendicantCoreGuidelight.java index 8825fd6713b..c88230090a1 100644 --- a/Mage.Sets/src/mage/cards/m/MendicantCoreGuidelight.java +++ b/Mage.Sets/src/mage/cards/m/MendicantCoreGuidelight.java @@ -1,45 +1,38 @@ package mage.cards.m; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.MaxSpeedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; -import mage.abilities.common.delayed.CopyNextSpellDelayedTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.Effect; -import mage.abilities.effects.common.CopyStackObjectEffect; import mage.abilities.effects.common.CopyTargetStackObjectEffect; import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; -import mage.constants.*; import mage.abilities.keyword.StartYourEnginesAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.filter.FilterPermanent; +import mage.constants.*; import mage.filter.FilterSpell; import mage.filter.StaticFilters; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterArtifactSpell; + +import java.util.UUID; /** - * * @author Jmlundeen */ public final class MendicantCoreGuidelight extends CardImpl { - private static final FilterSpell filter = new FilterSpell("an artifact spell"); + private static final FilterSpell filter = new FilterArtifactSpell("an artifact spell"); private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACTS); - static { - filter.add(CardType.ARTIFACT.getPredicate()); - } - public MendicantCoreGuidelight(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{W}{U}"); - + this.supertype.add(SuperType.LEGENDARY); this.subtype.add(SubType.ROBOT); this.power = new MageInt(0); @@ -53,7 +46,7 @@ public final class MendicantCoreGuidelight extends CardImpl { // Max speed -- Whenever you cast an artifact spell, you may pay {1}. If you do, copy it. Effect copyEffect = new CopyTargetStackObjectEffect(true) .setText("copy it. (The copy becomes a token.)"); - Effect doIfEffect = new DoIfCostPaid(copyEffect,new ManaCostsImpl<>("{1}")); + Effect doIfEffect = new DoIfCostPaid(copyEffect, new ManaCostsImpl<>("{1}")); Ability ability = new SpellCastControllerTriggeredAbility(doIfEffect, filter, false, SetTargetPointer.SPELL); this.addAbility(new MaxSpeedAbility(ability)); } diff --git a/Mage.Sets/src/mage/cards/o/OketrasMonument.java b/Mage.Sets/src/mage/cards/o/OketrasMonument.java index 5a78c71b598..cd1a3cef338 100644 --- a/Mage.Sets/src/mage/cards/o/OketrasMonument.java +++ b/Mage.Sets/src/mage/cards/o/OketrasMonument.java @@ -1,7 +1,6 @@ package mage.cards.o; -import java.util.UUID; import mage.ObjectColor; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; @@ -11,28 +10,24 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SuperType; -import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.FilterSpell; +import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.permanent.token.WarriorVigilantToken; +import java.util.UUID; + /** - * * @author fireshoes */ public final class OketrasMonument extends CardImpl { private static final FilterCard filter = new FilterCard("White creature spells"); - private static final FilterSpell filter2 = new FilterSpell("a creature spell"); static { filter.add(Predicates.and(new ColorPredicate(ObjectColor.WHITE), CardType.CREATURE.getPredicate())); } - static { - filter2.add(CardType.CREATURE.getPredicate()); - } public OketrasMonument(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); @@ -43,7 +38,10 @@ public final class OketrasMonument extends CardImpl { this.addAbility(new SimpleStaticAbility(new SpellsCostReductionControllerEffect(filter, 1))); // Whenever you cast a creature spell, create a 1/1 white Warrior creature token with vigilance. - this.addAbility(new SpellCastControllerTriggeredAbility(new CreateTokenEffect(new WarriorVigilantToken()), filter2, false)); + this.addAbility(new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new WarriorVigilantToken()), + StaticFilters.FILTER_SPELL_A_CREATURE, false + )); } private OketrasMonument(final OketrasMonument card) { diff --git a/Mage.Sets/src/mage/cards/r/RaulTroubleShooter.java b/Mage.Sets/src/mage/cards/r/RaulTroubleShooter.java index 6a7723eee0d..301cbec3813 100644 --- a/Mage.Sets/src/mage/cards/r/RaulTroubleShooter.java +++ b/Mage.Sets/src/mage/cards/r/RaulTroubleShooter.java @@ -1,7 +1,7 @@ package mage.cards.r; import mage.MageInt; -import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.MillCardsEachPlayerEffect; @@ -39,7 +39,7 @@ public final class RaulTroubleShooter extends CardImpl { this.toughness = new MageInt(4); // Once during each of your turns, you may cast a spell from among cards in your graveyard that were milled this turn. - this.addAbility(new CastFromGraveyardOnceEachTurnAbility(filter), new CardsMilledWatcher()); + this.addAbility(new CastFromGraveyardOnceDuringEachOfYourTurnAbility(filter), new CardsMilledWatcher()); // {T}: Each player mills a card. this.addAbility(new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/r/RhonassMonument.java b/Mage.Sets/src/mage/cards/r/RhonassMonument.java index 7c3db3a68d3..bb5b755a1d4 100644 --- a/Mage.Sets/src/mage/cards/r/RhonassMonument.java +++ b/Mage.Sets/src/mage/cards/r/RhonassMonument.java @@ -1,6 +1,5 @@ package mage.cards.r; -import java.util.UUID; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; @@ -15,30 +14,25 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SuperType; -import mage.constants.Zone; import mage.filter.FilterCard; -import mage.filter.FilterSpell; +import mage.filter.StaticFilters; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.ColorPredicate; import mage.target.common.TargetControlledCreaturePermanent; +import java.util.UUID; + /** - * * @author fireshoes */ public final class RhonassMonument extends CardImpl { private static final FilterCard filter = new FilterCard("Green creature spells"); - private static final FilterSpell filter2 = new FilterSpell("a creature spell"); static { filter.add(Predicates.and(new ColorPredicate(ObjectColor.GREEN), CardType.CREATURE.getPredicate())); } - static { - filter2.add(CardType.CREATURE.getPredicate()); - } - public RhonassMonument(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); @@ -49,7 +43,7 @@ public final class RhonassMonument extends CardImpl { // Whenever you cast a creature spell, target creature you control gets +2/+2 and gains trample until end of turn. Ability ability = new SpellCastControllerTriggeredAbility(new BoostTargetEffect(2, 2, Duration.EndOfTurn) - .setText("target creature you control gets +2/+2"), filter2, false); + .setText("target creature you control gets +2/+2"), StaticFilters.FILTER_SPELL_A_CREATURE, false); Effect effect = new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn); effect.setText("and gains trample until end of turn"); ability.addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/r/RivazOfTheClaw.java b/Mage.Sets/src/mage/cards/r/RivazOfTheClaw.java index d206b3ef84d..dccdbe27503 100644 --- a/Mage.Sets/src/mage/cards/r/RivazOfTheClaw.java +++ b/Mage.Sets/src/mage/cards/r/RivazOfTheClaw.java @@ -1,7 +1,7 @@ package mage.cards.r; import mage.MageInt; -import mage.abilities.common.CastFromGraveyardOnceEachTurnAbility; +import mage.abilities.common.CastFromGraveyardOnceDuringEachOfYourTurnAbility; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.ExileSourceEffect; @@ -50,7 +50,7 @@ public final class RivazOfTheClaw extends CardImpl { this.addAbility(new ConditionalAnyColorManaAbility(2, new ConditionalSpellManaBuilder(manaAbilityFilter))); // Once during each of your turns, you may cast a Dragon creature spell from your graveyard. - this.addAbility(new CastFromGraveyardOnceEachTurnAbility(staticAbilityFilter)); + this.addAbility(new CastFromGraveyardOnceDuringEachOfYourTurnAbility(staticAbilityFilter)); // Whenever you cast a Dragon creature spell from your graveyard, it gains "When this creature dies, exile it." this.addAbility(new SpellCastControllerTriggeredAbility( diff --git a/Mage.Sets/src/mage/sets/FinalFantasyCommander.java b/Mage.Sets/src/mage/sets/FinalFantasyCommander.java index 49773be2dfe..5ae88676e97 100644 --- a/Mage.Sets/src/mage/sets/FinalFantasyCommander.java +++ b/Mage.Sets/src/mage/sets/FinalFantasyCommander.java @@ -129,6 +129,8 @@ public final class FinalFantasyCommander extends ExpansionSet { cards.add(new SetCardInfo("Dragonskull Summit", 387, Rarity.RARE, mage.cards.d.DragonskullSummit.class)); cards.add(new SetCardInfo("Drowned Catacomb", 388, Rarity.RARE, mage.cards.d.DrownedCatacomb.class)); cards.add(new SetCardInfo("Duskshell Crawler", 301, Rarity.COMMON, mage.cards.d.DuskshellCrawler.class)); + cards.add(new SetCardInfo("Edgar, Master Machinist", 169, Rarity.RARE, mage.cards.e.EdgarMasterMachinist.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Edgar, Master Machinist", 80, Rarity.RARE, mage.cards.e.EdgarMasterMachinist.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Elena, Turk Recruit", 133, Rarity.RARE, mage.cards.e.ElenaTurkRecruit.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Elena, Turk Recruit", 18, Rarity.RARE, mage.cards.e.ElenaTurkRecruit.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Endless Detour", 324, Rarity.RARE, mage.cards.e.EndlessDetour.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/EdgarMasterMachinistTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/EdgarMasterMachinistTest.java new file mode 100644 index 00000000000..639101c0d7a --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/fic/EdgarMasterMachinistTest.java @@ -0,0 +1,180 @@ +package org.mage.test.cards.single.fic; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class EdgarMasterMachinistTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.e.EdgarMasterMachinist Edgar, Master Machinist} {2}{R}{W} + * Legendary Creature — Human Artificer Noble + * Once during each of your turns, you may cast an artifact spell from your graveyard. If you cast a spell this way, that artifact enters tapped. + * Tools — Whenever Edgar attacks, it gets +X/+0 until end of turn, where X is the greatest mana value among artifacts you control. + * 2/4 + */ + private static final String edgar = "Edgar, Master Machinist"; + + @Test + public void test_cast_from_yard() { + addCard(Zone.BATTLEFIELD, playerA, edgar); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.GRAVEYARD, playerA, "Golgari Signet"); + addCard(Zone.GRAVEYARD, playerA, "Elite Vanguard"); + + checkPlayableAbility("can not cast Elite Vanguard", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Elite Vanguard", false); + checkPlayableAbility("can cast Golgari Signet", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Golgari Signet", true); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Golgari Signet"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Golgari Signet", 1); + assertTappedCount("Plains", true, 2); + assertTappedCount("Golgari Signet", true, 1); + } + + @Test + public void test_tapped_effect_wait_for_cleanup() { + // test to make sure the discarding of the "enters tapped effect" only happens when the spell leave the stack + addCard(Zone.BATTLEFIELD, playerA, edgar); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.GRAVEYARD, playerA, "Golgari Signet"); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Golgari Signet"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Golgari Signet", 1); + assertTappedCount("Mountain", true, 3); + assertTappedCount("Golgari Signet", true, 1); + } + + @Test + public void test_cast_limits() { + addCard(Zone.BATTLEFIELD, playerA, edgar); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.GRAVEYARD, playerA, "Golgari Signet"); + addCard(Zone.GRAVEYARD, playerA, "Bear Trap"); // has flash + + addCard(Zone.GRAVEYARD, playerB, "Orzhov Signet"); + + checkPlayableAbility("can not cast opponent Orzhov Signet", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Orzhov Signet", false); + checkPlayableAbility("can cast Golgari Signet", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Golgari Signet", true); + checkPlayableAbility("can cast Bear Trap", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Bear Trap", true); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Golgari Signet"); + + checkPlayableAbility("can not cast 2 per turn", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Bear Trap", false); + + checkPlayableAbility("can not cast opponent Orzhov Signet", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Orzhov Signet", false); + checkPlayableAbility("opp can not cast Orzhov Signet", 2, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Orzhov Signet", false); + checkPlayableAbility("can not cast Bear Trap on opp turn", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Bear Trap", false); + + castSpell(3, PhaseStep.UPKEEP, playerA, "Bear Trap"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Golgari Signet", 1); + assertPermanentCount(playerA, "Bear Trap", 1); + assertTappedCount("Bear Trap", true, 1); + } + + @Test + public void test_mdfc() { + addCard(Zone.BATTLEFIELD, playerA, edgar); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.GRAVEYARD, playerA, "Halvar, God of Battle"); + + checkPlayableAbility("can not cast Halvar, God of Battle", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Halvar, God of Battle", false); + checkPlayableAbility("can cast Sword of the Realms", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Sword of the Realms", true); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sword of the Realms"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Sword of the Realms", 1); + assertTappedCount("Plains", true, 2); + assertTappedCount("Sword of the Realms", true, 1); + } + + @Test + public void test_adventure() { + addCard(Zone.BATTLEFIELD, playerA, edgar); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.GRAVEYARD, playerA, "Horn of Valhalla"); + + checkPlayableAbility("can not cast Ysgard's Call", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Ysgard's Call", false); + checkPlayableAbility("can cast Horn of Valhalla", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Horn of Valhalla", true); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Horn of Valhalla"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Horn of Valhalla", 1); + assertTappedCount("Plains", true, 2); + assertTappedCount("Horn of Valhalla", true, 1); + } + + @Test + public void test_remand_recast() { + addCard(Zone.BATTLEFIELD, playerA, edgar); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + addCard(Zone.HAND, playerB, "Remand"); + + addCard(Zone.GRAVEYARD, playerA, "Golgari Signet"); + addCard(Zone.GRAVEYARD, playerA, "Elite Vanguard"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Golgari Signet"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Remand", "Golgari Signet", "Golgari Signet"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Golgari Signet"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Golgari Signet", 1); + assertGraveyardCount(playerB, "Remand", 1); + assertTappedCount("Plains", true, 4); + assertTappedCount("Golgari Signet", false, 1); + } + + @Test + public void test_blink() { + addCard(Zone.BATTLEFIELD, playerA, edgar); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + addCard(Zone.HAND, playerA, "Cloudshift", 1); + addCard(Zone.GRAVEYARD, playerA, "Memnite"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + checkPermanentTapped("Memnite entered tapped", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite", true, 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", "Memnite"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertGraveyardCount(playerA, "Cloudshift", 1); + assertTappedCount("Memnite", false, 1); + } +} diff --git a/Mage/src/main/java/mage/MageIdentifier.java b/Mage/src/main/java/mage/MageIdentifier.java index b88d00f8a9d..ecd934256a5 100644 --- a/Mage/src/main/java/mage/MageIdentifier.java +++ b/Mage/src/main/java/mage/MageIdentifier.java @@ -19,7 +19,8 @@ public enum MageIdentifier { // e.g. [[Johann, Apprentice Sorcerer]] // "Once each turn, you may cast an instant or sorcery spell from the top of your library." // - CastFromGraveyardOnceWatcher, + OnceOnYourTurnCastFromGraveyard, + OnceOnYourTurnCastFromGraveyardEntersTapped, OnceEachTurnCastWatcher, HaukensInsightWatcher, IntrepidPaleontologistWatcher, diff --git a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceDuringEachOfYourTurnAbility.java b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceDuringEachOfYourTurnAbility.java new file mode 100644 index 00000000000..2da7dc64489 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceDuringEachOfYourTurnAbility.java @@ -0,0 +1,190 @@ +package mage.abilities.common; + +import mage.MageIdentifier; +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.Costs; +import mage.abilities.costs.CostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.common.replacement.MorEnteringTappedEffect; +import mage.cards.Card; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * Once during each of your turns, you may cast... from your graveyard + *

+ * See Lurrus of the Dream Den and Rivaz of the Claw + * + * @author weirddan455, Susucr + */ +public class CastFromGraveyardOnceDuringEachOfYourTurnAbility extends SimpleStaticAbility { + + public CastFromGraveyardOnceDuringEachOfYourTurnAbility(FilterCard filter) { + this(filter, (Cost) null); + } + + public CastFromGraveyardOnceDuringEachOfYourTurnAbility(FilterCard filter, Cost additionalCost) { + this(filter, additionalCost, MageIdentifier.OnceOnYourTurnCastFromGraveyard); + } + + public CastFromGraveyardOnceDuringEachOfYourTurnAbility(FilterCard filter, MageIdentifier mageIdentifier) { + this(filter, null, mageIdentifier); + } + + public CastFromGraveyardOnceDuringEachOfYourTurnAbility(FilterCard filter, Cost additionalCost, MageIdentifier mageIdentifier) { + super(new CastFromGraveyardOnceEffect(filter, additionalCost, mageIdentifier)); + this.addWatcher(new CastFromGraveyardOnceWatcher()); + switch (mageIdentifier) { + case OnceOnYourTurnCastFromGraveyard: + case OnceOnYourTurnCastFromGraveyardEntersTapped: + this.setIdentifier(mageIdentifier); + break; + default: + throw new IllegalArgumentException("Wrong code usage: only specific MageIdentifier are currently supported"); + } + } + + private CastFromGraveyardOnceDuringEachOfYourTurnAbility(final CastFromGraveyardOnceDuringEachOfYourTurnAbility ability) { + super(ability); + } + + @Override + public CastFromGraveyardOnceDuringEachOfYourTurnAbility copy() { + return new CastFromGraveyardOnceDuringEachOfYourTurnAbility(this); + } +} + +class CastFromGraveyardOnceEffect extends AsThoughEffectImpl { + + private final FilterCard filter; + private final Cost additionalCost; + private final MageIdentifier mageIdentifier; + + CastFromGraveyardOnceEffect(FilterCard filter, Cost additionalCost, MageIdentifier mageIdentifier) { + super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); + this.filter = filter; + this.staticText = "Once during each of your turns, you may cast " + filter.getMessage() + + (filter.getMessage().contains("your graveyard") ? "" : " from your graveyard") + + (additionalCost == null ? "" : " by " + additionalCost.getText() + " in addition to paying its other costs."); + this.additionalCost = additionalCost; + this.mageIdentifier = mageIdentifier; + } + + private CastFromGraveyardOnceEffect(final CastFromGraveyardOnceEffect effect) { + super(effect); + this.filter = effect.filter; + this.additionalCost = effect.additionalCost; + this.mageIdentifier = effect.mageIdentifier; + } + + @Override + public CastFromGraveyardOnceEffect copy() { + return new CastFromGraveyardOnceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); + } + + @Override + public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); + CastFromGraveyardOnceWatcher watcher = game.getState().getWatcher(CastFromGraveyardOnceWatcher.class); + Card cardToCast = game.getCard(objectId); + if (controller == null || sourcePermanent == null || watcher == null || cardToCast == null + || !game.isActivePlayer(playerId) // only during your turn + || !source.isControlledBy(playerId) // only you may cast + || !Zone.GRAVEYARD.equals(game.getState().getZone(objectId)) // from graveyard + || !cardToCast.getOwnerId().equals(playerId) // only your graveyard + || !(affectedAbility instanceof SpellAbility) // characteristics to check + || watcher.abilityUsed(new MageObjectReference(sourcePermanent, game)) // once per turn + ) { + return false; + } + SpellAbility spellAbility = (SpellAbility) affectedAbility; + Card cardToCheck = spellAbility.getCharacteristics(game); + if (spellAbility.getManaCosts().isEmpty()) { + return false; + } + Set allowedToBeCastNow = spellAbility.spellCanBeActivatedNow(playerId, game); + if (!allowedToBeCastNow.contains(MageIdentifier.Default) + || !filter.match(cardToCheck, playerId, source, game)) { + return false; + } + if (additionalCost != null) { + Costs costs = new CostsImpl<>(); + costs.add(additionalCost); + controller.setCastSourceIdWithAlternateMana( + objectId, spellAbility.getManaCosts(), + costs, mageIdentifier); + } + return true; + } +} + +class CastFromGraveyardOnceWatcher extends Watcher { + + // TODO: we might want to store (approver, approving ability) instead on the odd chance there + // is more than one such ability on a given approver. (event.getApprovingObject() has + // the exact ability, but not sure its id is stable enough.) + // Set of each approver that approved casting a spell this turn (and is thus done for the turn) + private final Set usedFrom = new HashSet<>(); + + CastFromGraveyardOnceWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (!GameEvent.EventType.SPELL_CAST.equals(event.getType())) { + return; + } + if (event.hasApprovingIdentifier(MageIdentifier.OnceOnYourTurnCastFromGraveyard)) { + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); + return; + } + if (event.hasApprovingIdentifier(MageIdentifier.OnceOnYourTurnCastFromGraveyardEntersTapped)) { + usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); + // The cast (most likely permanent) spell enters the battlefield tapped. + Spell target = game.getSpell(event.getTargetId()); + if (target != null) { + MageObjectReference mor = new MageObjectReference(target, game); + game.getState().addEffect( + new MorEnteringTappedEffect(mor), + event.getApprovingObject().getApprovingAbility() // ability that approved the cast is the source of the tapping. + ); + } + return; + } + } + + @Override + public void reset() { + super.reset(); + usedFrom.clear(); + } + + boolean abilityUsed(MageObjectReference mor) { + return usedFrom.contains(mor); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java b/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java deleted file mode 100644 index 3a0f520b724..00000000000 --- a/Mage/src/main/java/mage/abilities/common/CastFromGraveyardOnceEachTurnAbility.java +++ /dev/null @@ -1,150 +0,0 @@ -package mage.abilities.common; - -import mage.MageIdentifier; -import mage.MageObjectReference; -import mage.abilities.Ability; -import mage.abilities.SpellAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.Costs; -import mage.abilities.costs.CostsImpl; -import mage.abilities.effects.AsThoughEffectImpl; -import mage.cards.Card; -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.watchers.Watcher; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -/** - * Once during each of your turns, you may cast... from your graveyard - *

- * See Lurrus of the Dream Den and Rivaz of the Claw - * - * @author weirddan455 - */ -public class CastFromGraveyardOnceEachTurnAbility extends SimpleStaticAbility { - - public CastFromGraveyardOnceEachTurnAbility(FilterCard filter) { - this(filter, null); - } - - public CastFromGraveyardOnceEachTurnAbility(FilterCard filter, Cost additionalCost) { - super(new CastFromGraveyardOnceEffect(filter, additionalCost)); - this.addWatcher(new CastFromGraveyardOnceWatcher()); - this.setIdentifier(MageIdentifier.CastFromGraveyardOnceWatcher); - } - - private CastFromGraveyardOnceEachTurnAbility(final CastFromGraveyardOnceEachTurnAbility ability) { - super(ability); - } - - @Override - public CastFromGraveyardOnceEachTurnAbility copy() { - return new CastFromGraveyardOnceEachTurnAbility(this); - } -} - -class CastFromGraveyardOnceEffect extends AsThoughEffectImpl { - - private final FilterCard filter; - private final Cost additionalCost; - - CastFromGraveyardOnceEffect(FilterCard filter, Cost additionalCost) { - super(AsThoughEffectType.CAST_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); - this.filter = filter; - this.staticText = "Once during each of your turns, you may cast " + filter.getMessage() - + (filter.getMessage().contains("your graveyard") ? "" : " from your graveyard") - + (additionalCost == null ? "" : " by " + additionalCost.getText() + " in addition to paying its other costs."); - this.additionalCost = additionalCost; - } - - private CastFromGraveyardOnceEffect(final CastFromGraveyardOnceEffect effect) { - super(effect); - this.filter = effect.filter; - this.additionalCost = effect.additionalCost; - } - - @Override - public CastFromGraveyardOnceEffect copy() { - return new CastFromGraveyardOnceEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - throw new IllegalArgumentException("Wrong code usage: can't call applies method on empty affectedAbility"); - } - - @Override - public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) { - Player controller = game.getPlayer(source.getControllerId()); - Permanent sourcePermanent = source.getSourcePermanentIfItStillExists(game); - CastFromGraveyardOnceWatcher watcher = game.getState().getWatcher(CastFromGraveyardOnceWatcher.class); - Card cardToCast = game.getCard(objectId); - if (controller == null || sourcePermanent == null || watcher == null || cardToCast == null) { - return false; - } - if (game.isActivePlayer(playerId) // only during your turn - && source.isControlledBy(playerId) // only you may cast - && Zone.GRAVEYARD.equals(game.getState().getZone(objectId)) // from graveyard - && cardToCast.getOwnerId().equals(playerId) // only your graveyard - && affectedAbility instanceof SpellAbility // characteristics to check - && watcher.abilityNotUsed(new MageObjectReference(sourcePermanent, game)) // once per turn - ) { - SpellAbility spellAbility = (SpellAbility) affectedAbility; - Card cardToCheck = spellAbility.getCharacteristics(game); - if (spellAbility.getManaCosts().isEmpty()) { - return false; - } - Set allowedToBeCastNow = spellAbility.spellCanBeActivatedNow(playerId, game); - if (allowedToBeCastNow.contains(MageIdentifier.Default)) { - boolean matched = filter.match(cardToCheck, playerId, source, game); - if (matched && additionalCost != null) { - Costs costs = new CostsImpl<>(); - costs.add(additionalCost); - controller.setCastSourceIdWithAlternateMana(objectId, spellAbility.getManaCosts(), - costs, MageIdentifier.CastFromGraveyardOnceWatcher); - } - return matched; - } - } - return false; - } -} - -class CastFromGraveyardOnceWatcher extends Watcher { - - private final Set usedFrom = new HashSet<>(); - - CastFromGraveyardOnceWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void watch(GameEvent event, Game game) { - if (GameEvent.EventType.SPELL_CAST.equals(event.getType()) - && event.hasApprovingIdentifier(MageIdentifier.CastFromGraveyardOnceWatcher)) { - usedFrom.add(event.getApprovingObject().getApprovingMageObjectReference()); - } - } - - @Override - public void reset() { - super.reset(); - usedFrom.clear(); - } - - boolean abilityNotUsed(MageObjectReference mor) { - return !usedFrom.contains(mor); - } -} diff --git a/Mage/src/main/java/mage/abilities/effects/common/replacement/MorEnteringTappedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/replacement/MorEnteringTappedEffect.java new file mode 100644 index 00000000000..66bc00ef654 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/replacement/MorEnteringTappedEffect.java @@ -0,0 +1,69 @@ +package mage.abilities.effects.common.replacement; + +import mage.MageObjectReference; +import mage.abilities.Ability; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.events.EntersTheBattlefieldEvent; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; + +/** + * Used to affect a spell on the stack. + * The permanent it may become enters tapped. + *

+ * Short-lived replacement effect, auto-cleanup if the mor is no longer a spell. + * + * @author Susucr + */ +public class MorEnteringTappedEffect extends ReplacementEffectImpl { + + private final MageObjectReference mor; + + public MorEnteringTappedEffect(MageObjectReference mor) { + super(Duration.OneUse, Outcome.Tap); + this.staticText = "That permanent enters the battlefield tapped"; + this.mor = mor; + } + + private MorEnteringTappedEffect(final MorEnteringTappedEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public MorEnteringTappedEffect copy() { + return new MorEnteringTappedEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Spell spell = game.getSpell(event.getSourceId()); + Spell morSpell = mor.getSpell(game); + if (morSpell == null) { + // cleanup if something other than resolving happens to the spell. + discard(); + return false; + } + return spell != null + && morSpell.getSourceId() == spell.getSourceId() + && morSpell.getZoneChangeCounter(game) == spell.getZoneChangeCounter(game); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget(); + if (permanent != null) { + permanent.setTapped(true); + } + return false; + } +} diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index ab982e0c53b..872dcfe0d60 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -97,6 +97,13 @@ public final class StaticFilters { FILTER_CARD_CREATURE_A.setLockedFilter(true); } + // for checks on cards to be cast as "a creature spell", this is a FilterCard, but the text is about spell + public static final FilterCreatureCard FILTER_CARD_A_CREATURE_SPELL = new FilterCreatureCard("a creature spell"); + + static { + FILTER_CARD_A_CREATURE_SPELL.setLockedFilter(true); + } + public static final FilterCreatureCard FILTER_CARD_CREATURE_YOUR_HAND = new FilterCreatureCard("a creature card from your hand"); static {