diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java index 1d07e69f7af..8f9b6def8be 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java @@ -1,10 +1,10 @@ package org.mage.plugins.card.dl.sources; -import mage.cards.repository.TokenRepository; - import java.util.HashMap; import java.util.Map; +import mage.cards.repository.TokenRepository; + /** * @author JayDi85 */ @@ -2787,6 +2787,7 @@ public class ScryfallImageSupportTokens { // EOE put("EOE/Drone", "https://api.scryfall.com/cards/teoe/3?format=image"); + put("EOE/Emblem Tezzeret", "https://api.scryfall.com/cards/teoe/11?format=image"); put("EOE/Human Soldier", "https://api.scryfall.com/cards/teoe/2?format=image"); put("EOE/Lander/1", "https://api.scryfall.com/cards/teoe/4?format=image"); put("EOE/Lander/2", "https://api.scryfall.com/cards/teoe/5?format=image"); diff --git a/Mage.Sets/src/mage/cards/a/AerialSurveyor.java b/Mage.Sets/src/mage/cards/a/AerialSurveyor.java index b8199c1a008..3313a035a90 100644 --- a/Mage.Sets/src/mage/cards/a/AerialSurveyor.java +++ b/Mage.Sets/src/mage/cards/a/AerialSurveyor.java @@ -13,10 +13,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.filter.common.FilterLandPermanent; import mage.filter.predicate.permanent.DefendingPlayerControlsSourceAttackingPredicate; import mage.game.Controllable; @@ -32,8 +30,6 @@ import java.util.stream.Collectors; */ public final class AerialSurveyor extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard(SubType.PLAINS); - public AerialSurveyor(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{W}"); @@ -45,7 +41,7 @@ public final class AerialSurveyor extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever Aerial Surveyor attacks, if defending player controls more lands than you, search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle. - this.addAbility(new AttacksTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true)) + this.addAbility(new AttacksTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true)) .withInterveningIf(AerialSurveyorCondition.instance) .addHint(LandsYouControlHint.instance) .addHint(AerialSurveyorHint.instance)); diff --git a/Mage.Sets/src/mage/cards/a/AlpineGuide.java b/Mage.Sets/src/mage/cards/a/AlpineGuide.java index 7ec0ffa6ed3..13ae8cd915a 100644 --- a/Mage.Sets/src/mage/cards/a/AlpineGuide.java +++ b/Mage.Sets/src/mage/cards/a/AlpineGuide.java @@ -13,7 +13,6 @@ import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.FilterPermanent; -import mage.filter.common.FilterBySubtypeCard; import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCardInLibrary; @@ -24,7 +23,7 @@ import java.util.UUID; */ public final class AlpineGuide extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.MOUNTAIN); + private static final FilterCard filter = new FilterCard(SubType.MOUNTAIN); private static final FilterPermanent filter2 = new FilterControlledPermanent(SubType.MOUNTAIN, "Mountain"); public AlpineGuide(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java b/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java index cf911245785..b92a884539b 100644 --- a/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java +++ b/Mage.Sets/src/mage/cards/a/AmbitiousFarmhand.java @@ -14,8 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.AbilityWord; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -25,13 +24,6 @@ import java.util.UUID; */ public final class AmbitiousFarmhand extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - public AmbitiousFarmhand(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); @@ -43,7 +35,7 @@ public final class AmbitiousFarmhand extends CardImpl { // When Ambitious Farmhand enters the battlefield, you may search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), true + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true), true )); // Coven—{1}{W}{W}: Transform Ambitious Farmhand. Activate only if you control three or more creatures with different powers. diff --git a/Mage.Sets/src/mage/cards/a/AntiVenomHorrifyingHealer.java b/Mage.Sets/src/mage/cards/a/AntiVenomHorrifyingHealer.java new file mode 100644 index 00000000000..f8dbc3c8602 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AntiVenomHorrifyingHealer.java @@ -0,0 +1,89 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.CastFromEverywhereSourceCondition; +import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +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.counters.CounterType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCardInYourGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AntiVenomHorrifyingHealer extends CardImpl { + + public AntiVenomHorrifyingHealer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{W}{W}{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SYMBIOTE); + this.subtype.add(SubType.HERO); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // When Anti-Venom enters, if he was cast, return target creature card from your graveyard to the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()) + .withInterveningIf(CastFromEverywhereSourceCondition.instance); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD)); + this.addAbility(ability); + + // If damage would be dealt to Anti-Venom, prevent that damage and put that many +1/+1 counters on him. + this.addAbility(new SimpleStaticAbility(new AntiVenomHorrifyingHealerEffect())); + } + + private AntiVenomHorrifyingHealer(final AntiVenomHorrifyingHealer card) { + super(card); + } + + @Override + public AntiVenomHorrifyingHealer copy() { + return new AntiVenomHorrifyingHealer(this); + } +} + +class AntiVenomHorrifyingHealerEffect extends PreventionEffectImpl { + + AntiVenomHorrifyingHealerEffect() { + super(Duration.WhileOnBattlefield, Integer.MAX_VALUE, false, false); + staticText = "if damage would be dealt to {this}, prevent that damage and put that many +1/+1 counters on him"; + } + + private AntiVenomHorrifyingHealerEffect(final AntiVenomHorrifyingHealerEffect effect) { + super(effect); + } + + @Override + public AntiVenomHorrifyingHealerEffect copy() { + return new AntiVenomHorrifyingHealerEffect(this); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return super.applies(event, source, game) + && event.getTargetId().equals(source.getSourceId()); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null) { + permanent.addCounters(CounterType.P1P1.createInstance(event.getAmount()), source.getControllerId(), source, game); + } + return super.replaceEvent(event, source, game); + } +} diff --git a/Mage.Sets/src/mage/cards/a/ArchaeomancersMap.java b/Mage.Sets/src/mage/cards/a/ArchaeomancersMap.java index 27cfcac06b0..221acf4cf58 100644 --- a/Mage.Sets/src/mage/cards/a/ArchaeomancersMap.java +++ b/Mage.Sets/src/mage/cards/a/ArchaeomancersMap.java @@ -9,10 +9,7 @@ import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.SuperType; import mage.constants.TargetController; -import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.filter.common.FilterLandPermanent; @@ -26,13 +23,10 @@ import java.util.UUID; */ public final class ArchaeomancersMap extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Plains cards"); - private static final FilterPermanent filter2 = new FilterLandPermanent("a land an opponent controls"); + private static final FilterPermanent filter = new FilterLandPermanent("a land an opponent controls"); static { - filter.add(SubType.PLAINS.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); - filter2.add(TargetController.OPPONENT.getControllerPredicate()); + filter.add(TargetController.OPPONENT.getControllerPredicate()); } public ArchaeomancersMap(UUID ownerId, CardSetInfo setInfo) { @@ -40,12 +34,12 @@ public final class ArchaeomancersMap extends CardImpl { // When Archaeomancer's Map enters the battlefield, search your library for up to two basic Plains cards, reveal them, put them into your hand, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0, 2, filter), true) + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_PLAINS), true) )); // Whenever a land enters the battlefield under an opponent's control, if that player controls more lands than you, you may put a land card from your hand onto the battlefield. this.addAbility(new EntersBattlefieldAllTriggeredAbility( - new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A), filter2 + new PutCardFromHandOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A), filter ).withInterveningIf(ArchaeomancersMapCondition.instance)); } diff --git a/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java b/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java index ca6455ad195..8c698857b28 100644 --- a/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java +++ b/Mage.Sets/src/mage/cards/a/ArmMountedAnchor.java @@ -18,7 +18,7 @@ import mage.cards.CardSetInfo; import mage.constants.AttachmentType; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.FilterCard; import mage.game.Game; import mage.util.CardUtil; @@ -30,6 +30,8 @@ import java.util.UUID; */ public final class ArmMountedAnchor extends CardImpl { + private static final FilterCard filter = new FilterCard(SubType.PIRATE); + public ArmMountedAnchor(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); this.subtype.add(SubType.EQUIPMENT); @@ -42,11 +44,12 @@ public final class ArmMountedAnchor extends CardImpl { this.addAbility(firstAbility); // Whenever equipped creature deals combat damage to a player, draw two cards. Then discard two cards unless you discard a Pirate card. - Ability drawAbility = new DealsDamageToAPlayerAttachedTriggeredAbility(new DrawCardSourceControllerEffect(2), "equipped creature", false); - DiscardCardCost cost = new DiscardCardCost(new FilterBySubtypeCard(SubType.PIRATE)); - cost.setText("Discard a Pirate card instead of discarding two cards"); + Ability drawAbility = new DealsDamageToAPlayerAttachedTriggeredAbility( + new DrawCardSourceControllerEffect(2), "equipped creature", false + ); drawAbility.addEffect(new DoIfCostPaid( - null, new DiscardControllerEffect(2), cost + null, new DiscardControllerEffect(2), + new DiscardCardCost(filter).setText("Discard a Pirate card instead of discarding two cards") ).setText("Then discard two cards unless you discard a Pirate card")); this.addAbility(drawAbility); diff --git a/Mage.Sets/src/mage/cards/a/ArtifactWard.java b/Mage.Sets/src/mage/cards/a/ArtifactWard.java index 919dbc77724..8f4bdfae93c 100644 --- a/Mage.Sets/src/mage/cards/a/ArtifactWard.java +++ b/Mage.Sets/src/mage/cards/a/ArtifactWard.java @@ -1,7 +1,6 @@ package mage.cards.a; -import java.util.UUID; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; @@ -9,11 +8,16 @@ import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.ProtectionAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; import mage.filter.common.FilterArtifactCard; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** * * @author MarcoMarin @@ -33,7 +37,7 @@ public final class ArtifactWard extends CardImpl { // Enchanted creature can't be blocked by artifact creatures. // Prevent all damage that would be dealt to enchanted creature by artifact sources. // Enchanted creature can't be the target of abilities from artifact sources. - this.addAbility(new SimpleStaticAbility( + this.addAbility(new SimpleStaticAbility( // TODO: Implement as separate abilities, this isn't quite the same as "Enchanted creature gains protection from artifacts" new GainAbilityAttachedEffect(new ProtectionAbility(new FilterArtifactCard("artifacts")), AttachmentType.AURA))); } diff --git a/Mage.Sets/src/mage/cards/b/BantPanorama.java b/Mage.Sets/src/mage/cards/b/BantPanorama.java index dbc71082e43..87ff6af34b0 100644 --- a/Mage.Sets/src/mage/cards/b/BantPanorama.java +++ b/Mage.Sets/src/mage/cards/b/BantPanorama.java @@ -1,7 +1,5 @@ - package mage.cards.b; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; @@ -11,11 +9,15 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** * @author North */ @@ -24,7 +26,6 @@ public final class BantPanorama extends CardImpl { private static final FilterCard filter = new FilterCard("a basic Forest, Plains, or Island card"); static { - filter.add(CardType.LAND.getPredicate()); filter.add(SuperType.BASIC.getPredicate()); filter.add(Predicates.or( SubType.FOREST.getPredicate(), diff --git a/Mage.Sets/src/mage/cards/b/BindingTheOldGods.java b/Mage.Sets/src/mage/cards/b/BindingTheOldGods.java index 966e3bdc186..3d655a721f4 100644 --- a/Mage.Sets/src/mage/cards/b/BindingTheOldGods.java +++ b/Mage.Sets/src/mage/cards/b/BindingTheOldGods.java @@ -13,7 +13,6 @@ import mage.constants.SagaChapter; import mage.constants.SubType; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBySubtypeCard; import mage.target.TargetPermanent; import mage.target.common.TargetCardInLibrary; @@ -24,7 +23,7 @@ import java.util.UUID; */ public final class BindingTheOldGods extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.FOREST); + private static final FilterCard filter = new FilterCard(SubType.FOREST); public BindingTheOldGods(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}{G}"); @@ -33,15 +32,18 @@ public final class BindingTheOldGods extends CardImpl { // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.) SagaAbility sagaAbility = new SagaAbility(this); + // I — Destroy target nonland permanent an opponent controls. sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_I, new DestroyTargetEffect(), new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND) ); + // II — Search your library for a Forest card, put it onto the battlefield tapped, then shuffle your library. sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true) ); + // III — Creatures you control gain deathtouch until end of turn. sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new GainAbilityControlledEffect(DeathtouchAbility.getInstance(), Duration.EndOfTurn, diff --git a/Mage.Sets/src/mage/cards/b/Blink.java b/Mage.Sets/src/mage/cards/b/Blink.java index 3189879ab0a..98940ff92fc 100644 --- a/Mage.Sets/src/mage/cards/b/Blink.java +++ b/Mage.Sets/src/mage/cards/b/Blink.java @@ -51,7 +51,7 @@ public final class Blink extends CardImpl { this, SagaChapter.CHAPTER_IV, new CreateTokenEffect(new AlienAngelToken()) ); - this.addAbility(sagaAbility); + this.addAbility(sagaAbility); //TODO: These should be a single AddChapterEffect, but currently XMage does not support noncontiguous Saga chapters } private Blink(final Blink card) { diff --git a/Mage.Sets/src/mage/cards/b/BloodsoakedReveler.java b/Mage.Sets/src/mage/cards/b/BloodsoakedReveler.java index bd266074285..1c5d37419f5 100644 --- a/Mage.Sets/src/mage/cards/b/BloodsoakedReveler.java +++ b/Mage.Sets/src/mage/cards/b/BloodsoakedReveler.java @@ -2,7 +2,6 @@ package mage.cards.b; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.condition.Condition; import mage.abilities.condition.common.YouGainedLifeCondition; @@ -12,10 +11,14 @@ import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect; import mage.abilities.hint.ConditionHint; import mage.abilities.hint.Hint; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; import mage.game.permanent.token.BloodToken; +import mage.watchers.common.PlayerGainedLifeWatcher; import java.util.UUID; @@ -40,7 +43,7 @@ public final class BloodsoakedReveler extends CardImpl { this.addAbility(new BeginningOfEndStepTriggeredAbility( TargetController.YOU, new CreateTokenEffect(new BloodToken()), false, condition - ).addHint(hint)); + ).addHint(hint), new PlayerGainedLifeWatcher()); // {4}{B}: Each opponent loses 2 life and you gain 2 life. Ability ability = new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/b/BloomvineRegent.java b/Mage.Sets/src/mage/cards/b/BloomvineRegent.java index 88f7e8cb72d..1b1e0b1ed7c 100644 --- a/Mage.Sets/src/mage/cards/b/BloomvineRegent.java +++ b/Mage.Sets/src/mage/cards/b/BloomvineRegent.java @@ -1,40 +1,31 @@ package mage.cards.b; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.search.SearchLibraryPutOntoBattlefieldTappedRestInHandEffect; -import mage.cards.OmenCard; -import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; -import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.OmenCard; import mage.constants.CardType; -import mage.constants.SuperType; +import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; -import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.common.FilterBasicCard; import mage.filter.common.FilterCreaturePermanent; -import mage.target.Target; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author Jmlundeen */ public final class BloomvineRegent extends OmenCard { - private static final FilterCard filter = new FilterCard("basic Forest cards"); - - static { - filter.add(SubType.FOREST.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST, "basic Forest cards"); public BloomvineRegent(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{3}{G}{G}", "Claim Territory", "{2}{G}"); - + this.subtype.add(SubType.DRAGON); this.power = new MageInt(4); this.toughness = new MageInt(5); diff --git a/Mage.Sets/src/mage/cards/b/BohnBeguilingBalladeer.java b/Mage.Sets/src/mage/cards/b/BohnBeguilingBalladeer.java index 2bae253674d..825caef29d8 100644 --- a/Mage.Sets/src/mage/cards/b/BohnBeguilingBalladeer.java +++ b/Mage.Sets/src/mage/cards/b/BohnBeguilingBalladeer.java @@ -4,18 +4,14 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.CastSecondSpellTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.combat.GoadTargetEffect; import mage.abilities.keyword.ForetellAbility; -import mage.cards.*; -import mage.constants.*; -import mage.filter.common.FilterNonlandCard; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.game.Game; -import mage.players.Player; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.target.common.TargetOpponentsCreaturePermanent; -import mage.util.CardUtil; import java.util.UUID; @@ -34,7 +30,7 @@ public final class BohnBeguilingBalladeer extends CardImpl { this.toughness = new MageInt(3); // Each nonland card in your hand without foretell has foretell. Its foretell cost is equal to its mana cost reduced by {2}. - this.addAbility(new SimpleStaticAbility(new EdginLarcenousLutenistEffect())); + this.addAbility(new SimpleStaticAbility(ForetellAbility.makeAddForetellEffect())); // Whenever you cast your second spell each turn, goad target creature an opponent controls. Ability ability = new CastSecondSpellTriggeredAbility(new GoadTargetEffect()); @@ -51,69 +47,3 @@ public final class BohnBeguilingBalladeer extends CardImpl { return new BohnBeguilingBalladeer(this); } } - -class EdginLarcenousLutenistEffect extends ContinuousEffectImpl { - - private static final FilterNonlandCard filter = new FilterNonlandCard(); - - static { - filter.add(Predicates.not(new AbilityPredicate(ForetellAbility.class))); - } - - EdginLarcenousLutenistEffect() { - super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.staticText = "Each nonland card in your hand without foretell has foretell. Its foretell cost is equal to its mana cost reduced by {2}"; - } - - private EdginLarcenousLutenistEffect(final EdginLarcenousLutenistEffect effect) { - super(effect); - } - - @Override - public EdginLarcenousLutenistEffect copy() { - return new EdginLarcenousLutenistEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - for (Card card : controller.getHand().getCards(filter, game)) { - ForetellAbility foretellAbility = null; - if (card instanceof SplitCard) { - String leftHalfCost = CardUtil.reduceCost(((SplitCard) card).getLeftHalfCard().getManaCost(), 2).getText(); - String rightHalfCost = CardUtil.reduceCost(((SplitCard) card).getRightHalfCard().getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); - } else if (card instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - // If front side of MDFC is land, do nothing as Dream Devourer does not apply to lands - // MDFC cards in hand are considered lands if front side is land - if (!leftHalfCard.isLand(game)) { - String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), 2).getText(); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); - if (rightHalfCard.isLand(game)) { - foretellAbility = new ForetellAbility(card, leftHalfCost); - } else { - String rightHalfCost = CardUtil.reduceCost(rightHalfCard.getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); - } - } - } else if (card instanceof CardWithSpellOption) { - String creatureCost = CardUtil.reduceCost(card.getMainCard().getManaCost(), 2).getText(); - String spellCost = CardUtil.reduceCost(((CardWithSpellOption) card).getSpellCard().getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, creatureCost, spellCost); - } else { - String costText = CardUtil.reduceCost(card.getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, costText); - } - if (foretellAbility != null) { - foretellAbility.setSourceId(card.getId()); - foretellAbility.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card, foretellAbility); - } - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/b/BoseijuReachesSkyward.java b/Mage.Sets/src/mage/cards/b/BoseijuReachesSkyward.java index a7d623109af..120cf07a3ab 100644 --- a/Mage.Sets/src/mage/cards/b/BoseijuReachesSkyward.java +++ b/Mage.Sets/src/mage/cards/b/BoseijuReachesSkyward.java @@ -10,9 +10,9 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SagaChapter; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; -import mage.filter.common.FilterLandCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInYourGraveyard; @@ -23,13 +23,7 @@ import java.util.UUID; */ public final class BoseijuReachesSkyward extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Forest cards"); - private static final FilterCard filter2 = new FilterLandCard("land card from your graveyard"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST, "basic Forest cards"); public BoseijuReachesSkyward(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{G}"); @@ -52,7 +46,7 @@ public final class BoseijuReachesSkyward extends CardImpl { sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_II, SagaChapter.CHAPTER_II, new PutOnLibraryTargetEffect(true), - new TargetCardInYourGraveyard(0, 1, filter2) + new TargetCardInYourGraveyard(0, 1, StaticFilters.FILTER_CARD_LAND_FROM_YOUR_GRAVEYARD) ); // III — Exile this Saga, then return it to the battlefield transformed under your control. diff --git a/Mage.Sets/src/mage/cards/b/BountifulLandscape.java b/Mage.Sets/src/mage/cards/b/BountifulLandscape.java index fd792e43046..b456c364b9e 100644 --- a/Mage.Sets/src/mage/cards/b/BountifulLandscape.java +++ b/Mage.Sets/src/mage/cards/b/BountifulLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class BountifulLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Forest, Island, or Mountain card"); + private static final FilterCard filter = new FilterBasicCard("a basic Forest, Island, or Mountain card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/c/CabalStronghold.java b/Mage.Sets/src/mage/cards/c/CabalStronghold.java index 2f2d4ccfc52..75c31ac39b4 100644 --- a/Mage.Sets/src/mage/cards/c/CabalStronghold.java +++ b/Mage.Sets/src/mage/cards/c/CabalStronghold.java @@ -1,13 +1,12 @@ - package mage.cards.c; -import java.util.UUID; - import mage.Mana; import mage.abilities.Ability; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.hint.Hint; import mage.abilities.hint.ValueHint; import mage.abilities.mana.ColorlessManaAbility; import mage.abilities.mana.DynamicManaAbility; @@ -16,21 +15,26 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.filter.common.FilterControlledLandPermanent; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; + +import java.util.UUID; /** * @author JRHerlehy - * Created on 4/7/18. + * Created on 4/7/18. */ public final class CabalStronghold extends CardImpl { - private static final FilterControlledLandPermanent filter = new FilterControlledLandPermanent("basic Swamp you control"); + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SWAMP, "basic Swamp you control"); static { filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.SWAMP.getPredicate()); } + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + private static final Hint hint = new ValueHint("Basic Swamps you control", xValue); + public CabalStronghold(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); @@ -38,9 +42,9 @@ public final class CabalStronghold extends CardImpl { this.addAbility(new ColorlessManaAbility()); // {3}, {T}: Add {B} for each basic Swamp you control. - Ability ability = new DynamicManaAbility(Mana.BlackMana(1), new PermanentsOnBattlefieldCount(filter), new GenericManaCost(3)); + Ability ability = new DynamicManaAbility(Mana.BlackMana(1), xValue, new GenericManaCost(3)); ability.addCost(new TapSourceCost()); - this.addAbility(ability.addHint(new ValueHint("Basic Swamps you control", new PermanentsOnBattlefieldCount(filter)))); + this.addAbility(ability.addHint(hint)); } private CabalStronghold(final CabalStronghold card) { diff --git a/Mage.Sets/src/mage/cards/c/CanoptekWraith.java b/Mage.Sets/src/mage/cards/c/CanoptekWraith.java index 16e238996e4..b0579a1a5e1 100644 --- a/Mage.Sets/src/mage/cards/c/CanoptekWraith.java +++ b/Mage.Sets/src/mage/cards/c/CanoptekWraith.java @@ -12,13 +12,10 @@ import mage.abilities.keyword.CantBeBlockedSourceAbility; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterLandCard; import mage.filter.predicate.Predicate; import mage.game.Game; import mage.game.permanent.Permanent; @@ -101,7 +98,8 @@ class CanoptekWraithEffect extends OneShotEffect { if (permanent == null) { return false; } - FilterCard filter = new FilterBasicLandCard("basic land cards with the same name as the chosen land"); + FilterCard filter = new FilterLandCard("basic land cards with the same name as the chosen land"); + filter.add(SuperType.BASIC.getPredicate()); filter.add(new CanoptekWraithPredicate(permanent)); TargetCardInLibrary targetCard = new TargetCardInLibrary(0, 2, filter); player.searchLibrary(targetCard, source, game); @@ -129,4 +127,4 @@ class CanoptekWraithPredicate implements Predicate { public boolean apply(Card input, Game game) { return CardUtil.haveSameNames(permanent, input); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/c/CharitableLevy.java b/Mage.Sets/src/mage/cards/c/CharitableLevy.java index bbc6d4a8e7a..a95a84a5a4a 100644 --- a/Mage.Sets/src/mage/cards/c/CharitableLevy.java +++ b/Mage.Sets/src/mage/cards/c/CharitableLevy.java @@ -20,7 +20,6 @@ import mage.constants.TargetController; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBySubtypeCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -39,7 +38,7 @@ public final class CharitableLevy extends CardImpl { private static final Condition condition = new SourceHasCounterCondition(CounterType.COLLECTION, 3); - private static final FilterCard filterPlains = new FilterBySubtypeCard(SubType.PLAINS); + private static final FilterCard filterPlains = new FilterCard(SubType.PLAINS); public CharitableLevy(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); diff --git a/Mage.Sets/src/mage/cards/c/ClayFiredBricks.java b/Mage.Sets/src/mage/cards/c/ClayFiredBricks.java index a6dd2fed8c7..06cc8042fe6 100644 --- a/Mage.Sets/src/mage/cards/c/ClayFiredBricks.java +++ b/Mage.Sets/src/mage/cards/c/ClayFiredBricks.java @@ -8,9 +8,7 @@ import mage.abilities.keyword.CraftAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.SubType; -import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -20,15 +18,13 @@ import java.util.UUID; */ public final class ClayFiredBricks extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard(SubType.PLAINS); - public ClayFiredBricks(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}"); this.secondSideCardClazz = mage.cards.c.CosmiumKiln.class; // When Clay-Fired Bricks enters the battlefield, search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. You gain 2 life. Ability ability = new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true) ); ability.addEffect(new GainLifeEffect(2)); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/c/ContaminatedLandscape.java b/Mage.Sets/src/mage/cards/c/ContaminatedLandscape.java index 33befbbf714..441fc40438e 100644 --- a/Mage.Sets/src/mage/cards/c/ContaminatedLandscape.java +++ b/Mage.Sets/src/mage/cards/c/ContaminatedLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class ContaminatedLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Plains, Island, or Swamp card"); + private static final FilterCard filter = new FilterBasicCard("a basic Plains, Island, or Swamp card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java b/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java index effdfb78226..cbcaf214e65 100644 --- a/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java +++ b/Mage.Sets/src/mage/cards/d/DamiaSageOfStone.java @@ -1,31 +1,29 @@ - package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.dynamicvalue.IntPlusDynamicValue; -import mage.abilities.dynamicvalue.MultipliedValue; -import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; -import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInHandCondition; +import mage.abilities.effects.common.DrawCardsEqualToDifferenceEffect; import mage.abilities.effects.common.SkipDrawStepEffect; import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.ComparisonType; import mage.constants.SubType; import mage.constants.SuperType; -import mage.constants.TargetController; -import mage.game.Game; -import mage.players.Player; + +import java.util.UUID; /** - * * @author emerald000 */ public final class DamiaSageOfStone extends CardImpl { + private static final Condition condition = new CardsInHandCondition(ComparisonType.FEWER_THAN, 7); + public DamiaSageOfStone(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}{G}{U}"); this.supertype.add(SuperType.LEGENDARY); @@ -42,7 +40,7 @@ public final class DamiaSageOfStone extends CardImpl { this.addAbility(new SimpleStaticAbility(new SkipDrawStepEffect())); // At the beginning of your upkeep, if you have fewer than seven cards in hand, draw cards equal to the difference. - this.addAbility(new DamiaSageOfStoneTriggeredAbility()); + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DrawCardsEqualToDifferenceEffect(7)).withInterveningIf(condition)); } private DamiaSageOfStone(final DamiaSageOfStone card) { @@ -54,33 +52,3 @@ public final class DamiaSageOfStone extends CardImpl { return new DamiaSageOfStone(this); } } - -class DamiaSageOfStoneTriggeredAbility extends BeginningOfUpkeepTriggeredAbility { - - DamiaSageOfStoneTriggeredAbility() { - super(TargetController.YOU, new DrawCardSourceControllerEffect(new IntPlusDynamicValue(7, new MultipliedValue(CardsInControllerHandCount.ANY, -1))), false); - } - - private DamiaSageOfStoneTriggeredAbility(final DamiaSageOfStoneTriggeredAbility ability) { - super(ability); - } - - @Override - public DamiaSageOfStoneTriggeredAbility copy() { - return new DamiaSageOfStoneTriggeredAbility(this); - } - - @Override - public boolean checkInterveningIfClause(Game game) { - Player player = game.getPlayer(this.getControllerId()); - if (player != null) { - return player.getHand().size() < 7; - } - return false; - } - - @Override - public String getRule() { - return "At the beginning of your upkeep, if you have fewer than seven cards in hand, draw cards equal to the difference."; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DeceptiveLandscape.java b/Mage.Sets/src/mage/cards/d/DeceptiveLandscape.java index a795b780bd0..2b8bfcc9640 100644 --- a/Mage.Sets/src/mage/cards/d/DeceptiveLandscape.java +++ b/Mage.Sets/src/mage/cards/d/DeceptiveLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class DeceptiveLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Plains, Swamp, or Forest card"); + private static final FilterCard filter = new FilterBasicCard("a basic Plains, Swamp, or Forest card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/d/DoctorOctopusMasterPlanner.java b/Mage.Sets/src/mage/cards/d/DoctorOctopusMasterPlanner.java new file mode 100644 index 00000000000..72ae12cb57c --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DoctorOctopusMasterPlanner.java @@ -0,0 +1,58 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.CardsInHandCondition; +import mage.abilities.effects.common.DrawCardsEqualToDifferenceEffect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DoctorOctopusMasterPlanner extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.VILLAIN, "Villains"); + private static final Condition condition = new CardsInHandCondition(ComparisonType.FEWER_THAN, 8); + + public DoctorOctopusMasterPlanner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SCIENTIST); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(4); + this.toughness = new MageInt(8); + + // Other Villains you control get +2/+2. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + 2, 2, Duration.WhileOnBattlefield, filter, true + ))); + + // Your maximum hand size is eight. + this.addAbility(new SimpleStaticAbility(new MaximumHandSizeControllerEffect( + 8, Duration.WhileOnBattlefield, MaximumHandSizeControllerEffect.HandSizeModification.SET + ))); + + // At the beginning of your end step, if you have fewer than eight cards in hand, draw cards equal to the difference. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new DrawCardsEqualToDifferenceEffect(8)).withInterveningIf(condition)); + } + + private DoctorOctopusMasterPlanner(final DoctorOctopusMasterPlanner card) { + super(card); + } + + @Override + public DoctorOctopusMasterPlanner copy() { + return new DoctorOctopusMasterPlanner(this); + } +} diff --git a/Mage.Sets/src/mage/cards/d/DreamDevourer.java b/Mage.Sets/src/mage/cards/d/DreamDevourer.java index 0ed63bc1e30..688b87cf66d 100644 --- a/Mage.Sets/src/mage/cards/d/DreamDevourer.java +++ b/Mage.Sets/src/mage/cards/d/DreamDevourer.java @@ -1,27 +1,17 @@ package mage.cards.d; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.ForetellSourceControllerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.keyword.ForetellAbility; -import mage.cards.*; -import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; -import mage.constants.Zone; -import mage.filter.common.FilterNonlandCard; -import mage.filter.predicate.Predicates; -import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.game.Game; -import mage.players.Player; -import mage.util.CardUtil; +import mage.constants.SubType; + +import java.util.UUID; /** * @@ -38,7 +28,7 @@ public final class DreamDevourer extends CardImpl { this.toughness = new MageInt(3); // Each nonland card in your hand without foretell has foretell. Its foretell cost is equal to its mana cost reduced by 2. - this.addAbility(new SimpleStaticAbility(new DreamDevourerAddAbilityEffect())); + this.addAbility(new SimpleStaticAbility(ForetellAbility.makeAddForetellEffect())); // Whenever you foretell a card, Dream Devourer gets +2/+0 until end of turn. this.addAbility(new ForetellSourceControllerTriggeredAbility(new BoostSourceEffect(2, 0, Duration.EndOfTurn))); @@ -54,69 +44,3 @@ public final class DreamDevourer extends CardImpl { return new DreamDevourer(this); } } - -class DreamDevourerAddAbilityEffect extends ContinuousEffectImpl { - - private static final FilterNonlandCard filter = new FilterNonlandCard(); - - static { - filter.add(Predicates.not(new AbilityPredicate(ForetellAbility.class))); - } - - DreamDevourerAddAbilityEffect() { - super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.staticText = "Each nonland card in your hand without foretell has foretell. Its foretell cost is equal to its mana cost reduced by {2}"; - } - - private DreamDevourerAddAbilityEffect(final DreamDevourerAddAbilityEffect effect) { - super(effect); - } - - @Override - public DreamDevourerAddAbilityEffect copy() { - return new DreamDevourerAddAbilityEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller == null) { - return false; - } - for (Card card : controller.getHand().getCards(filter, game)) { - ForetellAbility foretellAbility = null; - if (card instanceof SplitCard) { - String leftHalfCost = CardUtil.reduceCost(((SplitCard) card).getLeftHalfCard().getManaCost(), 2).getText(); - String rightHalfCost = CardUtil.reduceCost(((SplitCard) card).getRightHalfCard().getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); - } else if (card instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - // If front side of MDFC is land, do nothing as Dream Devourer does not apply to lands - // MDFC cards in hand are considered lands if front side is land - if (!leftHalfCard.isLand(game)) { - String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), 2).getText(); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); - if (rightHalfCard.isLand(game)) { - foretellAbility = new ForetellAbility(card, leftHalfCost); - } else { - String rightHalfCost = CardUtil.reduceCost(rightHalfCard.getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); - } - } - } else if (card instanceof CardWithSpellOption) { - String creatureCost = CardUtil.reduceCost(card.getMainCard().getManaCost(), 2).getText(); - String spellCost = CardUtil.reduceCost(((CardWithSpellOption) card).getSpellCard().getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, creatureCost, spellCost); - } else { - String costText = CardUtil.reduceCost(card.getManaCost(), 2).getText(); - foretellAbility = new ForetellAbility(card, costText); - } - if (foretellAbility != null) { - foretellAbility.setSourceId(card.getId()); - foretellAbility.setControllerId(card.getOwnerId()); - game.getState().addOtherAbility(card, foretellAbility); - } - } - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/e/ElderfangRitualist.java b/Mage.Sets/src/mage/cards/e/ElderfangRitualist.java index 5d2cfdf3f0c..fa99c9a98ce 100644 --- a/Mage.Sets/src/mage/cards/e/ElderfangRitualist.java +++ b/Mage.Sets/src/mage/cards/e/ElderfangRitualist.java @@ -1,29 +1,28 @@ package mage.cards.e; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBySubtypeCard; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class ElderfangRitualist extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.ELF); + private static final FilterCard filter = new FilterCard(SubType.ELF, "another target Elf card from your graveyard"); + static { filter.add(AnotherPredicate.instance); - filter.setMessage("another target Elf card from your graveyard"); } public ElderfangRitualist(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/e/EntishRestoration.java b/Mage.Sets/src/mage/cards/e/EntishRestoration.java index 6df9fc8f39e..a330ef60b17 100644 --- a/Mage.Sets/src/mage/cards/e/EntishRestoration.java +++ b/Mage.Sets/src/mage/cards/e/EntishRestoration.java @@ -1,7 +1,5 @@ package mage.cards.e; -import java.util.UUID; - import mage.abilities.condition.common.FerociousCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.SacrificeControllerEffect; @@ -9,23 +7,20 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author Susucr */ public final class EntishRestoration extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("basic land cards"); - private static final String rule = "Search your library for up to two basic land cards, " + - "put them onto the battlefield tapped, then shuffle. " + - "If " + FerociousCondition.instance.toString() + ", instead search your library for up " + - "to three basic land cards, put them onto the battlefield tapped, then shuffle."; + "put them onto the battlefield tapped, then shuffle. " + + "If " + FerociousCondition.instance + ", instead search your library for up " + + "to three basic land cards, put them onto the battlefield tapped, then shuffle."; public EntishRestoration(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); @@ -35,14 +30,14 @@ public final class EntishRestoration extends CardImpl { // creature with power 4 or greater, instead search your library for up // to three basic land cards, put them onto the battlefield tapped, then shuffle. this.getSpellAbility().addEffect(new SacrificeControllerEffect( - StaticFilters.FILTER_LAND, 1, null + StaticFilters.FILTER_LAND, 1, null ).setText("Sacrifice a land.")); this.getSpellAbility().addEffect( - new ConditionalOneShotEffect( - new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 3, filter), true), - new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 2, filter), true), - FerociousCondition.instance, rule - ) + new ConditionalOneShotEffect( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 3, StaticFilters.FILTER_CARD_BASIC_LANDS), true), + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS), true), + FerociousCondition.instance, rule + ) ); } diff --git a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java index b9f8d113128..5425c44aba2 100644 --- a/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java +++ b/Mage.Sets/src/mage/cards/e/EtherealValkyrie.java @@ -1,23 +1,22 @@ package mage.cards.e; import mage.MageInt; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; -import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.ForetellAbility; -import mage.cards.*; +import mage.cards.Card; +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.game.events.GameEvent; import mage.players.Player; import mage.target.common.TargetCardInHand; -import mage.util.CardUtil; +import mage.watchers.common.ForetoldWatcher; import java.util.UUID; @@ -38,7 +37,7 @@ public final class EtherealValkyrie extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever Ethereal Valkyrie enters the battlefield or attacks, draw a card, then exile a card from your hand face down. It becomes foretold. Its foretell cost is its mana cost reduced by {2}. - this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new EtherealValkyrieEffect())); + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new EtherealValkyrieEffect()), new ForetoldWatcher()); } private EtherealValkyrie(final EtherealValkyrie card) { @@ -75,77 +74,15 @@ class EtherealValkyrieEffect extends OneShotEffect { if (controller == null) { return false; } - controller.drawCards(1, source, game); - TargetCardInHand targetCard = new TargetCardInHand(new FilterCard("card to exile face down. It becomes foretold.")); + TargetCardInHand targetCard = new TargetCardInHand(StaticFilters.FILTER_CARD).withChooseHint("to exile face down; it becomes foretold"); if (!controller.chooseTarget(Outcome.Benefit, targetCard, source, game)) { return false; } - - Card exileCard = game.getCard(targetCard.getFirstTarget()); - if (exileCard == null) { + Card card = game.getCard(targetCard.getFirstTarget()); + if (card == null) { return false; } - - // process Split, MDFC, and Adventure cards first - // note that 'Foretell Cost' refers to the main card (left) and 'Foretell Split Cost' refers to the (right) card if it exists - ForetellAbility foretellAbility = null; - if (exileCard instanceof SplitCard) { - String leftHalfCost = CardUtil.reduceCost(((SplitCard) exileCard).getLeftHalfCard().getManaCost(), 2).getText(); - String rightHalfCost = CardUtil.reduceCost(((SplitCard) exileCard).getRightHalfCard().getManaCost(), 2).getText(); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Cost", leftHalfCost); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Split Cost", rightHalfCost); - foretellAbility = new ForetellAbility(exileCard, leftHalfCost, rightHalfCost); - } else if (exileCard instanceof ModalDoubleFacedCard) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) exileCard).getLeftHalfCard(); - if (!leftHalfCard.isLand(game)) { // Only MDFC cards with a left side a land have a land on the right side too - String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), 2).getText(); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Cost", leftHalfCost); - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) exileCard).getRightHalfCard(); - if (rightHalfCard.isLand(game)) { - foretellAbility = new ForetellAbility(exileCard, leftHalfCost); - } else { - String rightHalfCost = CardUtil.reduceCost(rightHalfCard.getManaCost(), 2).getText(); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Split Cost", rightHalfCost); - foretellAbility = new ForetellAbility(exileCard, leftHalfCost, rightHalfCost); - } - } - } else if (exileCard instanceof CardWithSpellOption) { - String creatureCost = CardUtil.reduceCost(exileCard.getMainCard().getManaCost(), 2).getText(); - String spellCost = CardUtil.reduceCost(((CardWithSpellOption) exileCard).getSpellCard().getManaCost(), 2).getText(); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Cost", creatureCost); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Split Cost", spellCost); - foretellAbility = new ForetellAbility(exileCard, creatureCost, spellCost); - } else if (!exileCard.isLand(game)) { - // normal card - String costText = CardUtil.reduceCost(exileCard.getManaCost(), 2).getText(); - game.getState().setValue(exileCard.getId().toString() + "Foretell Cost", costText); - foretellAbility = new ForetellAbility(exileCard, costText); - } - - // All card types (including lands) must be exiled - UUID exileId = CardUtil.getExileZoneId(exileCard.getMainCard().getId().toString() + "foretellAbility", game); - controller.moveCardsToExile(exileCard, source, game, true, exileId, " Foretell Turn Number: " + game.getTurnNum()); - exileCard.setFaceDown(true, game); - - // all done pre-processing so stick the foretell cost effect onto the main card - // note that the card is not foretell'd into exile, it is put into exile and made foretold - // If the card is a non-land, it will not be exiled. - if (foretellAbility != null) { - // copy source and use it for the foretold effect on the exiled card - // bug #8673 - Ability copiedSource = source.copy(); - copiedSource.newId(); - copiedSource.setSourceId(exileCard.getId()); - game.getState().setValue(exileCard.getMainCard().getId().toString() + "Foretell Turn Number", game.getTurnNum()); - foretellAbility.setSourceId(exileCard.getId()); - foretellAbility.setControllerId(exileCard.getOwnerId()); - game.getState().addOtherAbility(exileCard, foretellAbility); - foretellAbility.activate(game, true); - ContinuousEffect effect = new ForetellAbility.ForetellAddCostEffect(new MageObjectReference(exileCard, game)); - game.addEffect(effect, copiedSource); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.FORETOLD, exileCard.getId(), null, null)); - } - return true; + return ForetellAbility.doExileBecomesForetold(card, game, source, 2); } } diff --git a/Mage.Sets/src/mage/cards/f/FieldTrip.java b/Mage.Sets/src/mage/cards/f/FieldTrip.java index 25989562648..c397e88f885 100644 --- a/Mage.Sets/src/mage/cards/f/FieldTrip.java +++ b/Mage.Sets/src/mage/cards/f/FieldTrip.java @@ -7,8 +7,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -18,12 +18,7 @@ import java.util.UUID; */ public final class FieldTrip extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Forest card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST); public FieldTrip(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); diff --git a/Mage.Sets/src/mage/cards/f/ForebodingLandscape.java b/Mage.Sets/src/mage/cards/f/ForebodingLandscape.java index fad15d154d7..bc6b5c61f57 100644 --- a/Mage.Sets/src/mage/cards/f/ForebodingLandscape.java +++ b/Mage.Sets/src/mage/cards/f/ForebodingLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class ForebodingLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Swamp, Forest, or Island card"); + private static final FilterCard filter = new FilterBasicCard("a basic Swamp, Forest, or Island card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheCoalition.java b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheCoalition.java index 85110624e88..3d968a4d851 100644 --- a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheCoalition.java +++ b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheCoalition.java @@ -1,32 +1,33 @@ - package mage.cards.f; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.LoseLifeOpponentsEffect; import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; import mage.filter.FilterPermanent; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author JayDi85 */ public final class ForerunnerOfTheCoalition extends CardImpl { - private static final FilterPermanent filterAnotherPirate = new FilterPermanent(SubType.PIRATE, "another " + SubType.PIRATE.toString()); + private static final FilterCard filter = new FilterCard(SubType.PIRATE); + private static final FilterPermanent filter2 + = new FilterControlledPermanent(SubType.PIRATE, "another Pirate you control"); static { - filterAnotherPirate.add(AnotherPredicate.instance); - filterAnotherPirate.add(TargetController.YOU.getControllerPredicate()); + filter2.add(AnotherPredicate.instance); } public ForerunnerOfTheCoalition(UUID ownerId, CardSetInfo setInfo) { @@ -39,15 +40,13 @@ public final class ForerunnerOfTheCoalition extends CardImpl { // When Forerunner of the Coalition enters the battlefield, you may search your library for a Pirate card, reveal it, then shuffle your library and put that card on top of it. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutOnLibraryEffect( - new TargetCardInLibrary(new FilterBySubtypeCard(SubType.PIRATE)), - true), true)); + new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(filter), true), true + )); // Whenever another Pirate you control enters, each opponent loses 1 life. - Ability ability = new EntersBattlefieldControlledTriggeredAbility( - Zone.BATTLEFIELD, new LoseLifeOpponentsEffect(1), - filterAnotherPirate, false); - this.addAbility(ability); + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new LoseLifeOpponentsEffect(1), filter2 + )); } private ForerunnerOfTheCoalition(final ForerunnerOfTheCoalition card) { diff --git a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheEmpire.java b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheEmpire.java index 1b47ba5a9a8..1029bcbb394 100644 --- a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheEmpire.java +++ b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheEmpire.java @@ -1,9 +1,7 @@ - package mage.cards.f; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; @@ -11,10 +9,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.TargetController; -import mage.constants.Zone; -import mage.filter.common.FilterBySubtypeCard; -import mage.filter.common.FilterCreaturePermanent; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -24,11 +22,9 @@ import java.util.UUID; */ public final class ForerunnerOfTheEmpire extends CardImpl { - private static final FilterCreaturePermanent filterAnyDinosaur = new FilterCreaturePermanent(SubType.DINOSAUR, "a " + SubType.DINOSAUR.toString()); - - static { - filterAnyDinosaur.add(TargetController.YOU.getControllerPredicate()); - } + private static final FilterCard filter = new FilterCard(SubType.DINOSAUR); + private static final FilterPermanent filter2 + = new FilterControlledPermanent(SubType.DINOSAUR, "Dinosaur you control"); public ForerunnerOfTheEmpire(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); @@ -39,23 +35,16 @@ public final class ForerunnerOfTheEmpire extends CardImpl { this.toughness = new MageInt(3); // When Forerunner of the Empire enters the battlefield, you may search your library for a Dinosaur card, reveal it, then shuffle your library and put that card on top of it. - this.addAbility( - new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutOnLibraryEffect( - new TargetCardInLibrary(new FilterBySubtypeCard(SubType.DINOSAUR)), - true - ), - true - ) - ); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(filter), true), true + )); // Whenever a Dinosaur you control enters, you may have Forerunner of the Empire deal 1 damage to each creature. - Ability ability = new EntersBattlefieldControlledTriggeredAbility( - Zone.BATTLEFIELD, - new DamageAllEffect(1, new FilterCreaturePermanent()).setText("have {this} deal 1 damage to each creature"), - filterAnyDinosaur, - true); - this.addAbility(ability); + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new DamageAllEffect(1, StaticFilters.FILTER_PERMANENT_CREATURE) + .setText("have {this} deal 1 damage to each creature"), + filter2, true + )); } private ForerunnerOfTheEmpire(final ForerunnerOfTheEmpire card) { diff --git a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheHeralds.java b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheHeralds.java index a2acbccc45e..7acdee5b7cc 100644 --- a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheHeralds.java +++ b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheHeralds.java @@ -1,34 +1,35 @@ package mage.cards.f; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; -import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.TargetController; +import mage.constants.SubType; import mage.counters.CounterType; +import mage.filter.FilterCard; import mage.filter.FilterPermanent; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author JayDi85 */ public final class ForerunnerOfTheHeralds extends CardImpl { - private static final FilterPermanent filterAnotherMerfolk = new FilterPermanent(SubType.MERFOLK, "another " + SubType.MERFOLK.toString()); + private static final FilterCard filter = new FilterCard(SubType.MERFOLK); + private static final FilterPermanent filter2 + = new FilterControlledPermanent(SubType.MERFOLK, "another Merfolk you control"); + static { - filterAnotherMerfolk.add(AnotherPredicate.instance); - filterAnotherMerfolk.add(TargetController.YOU.getControllerPredicate()); + filter2.add(AnotherPredicate.instance); } public ForerunnerOfTheHeralds(UUID ownerId, CardSetInfo setInfo) { @@ -39,21 +40,15 @@ public final class ForerunnerOfTheHeralds extends CardImpl { this.power = new MageInt(3); this.toughness = new MageInt(2); - // When Forerunner of the Heralds enters the battlefield, you may search your library for a Merfolk card, reveal it, then shuffle your library and put that card on top of it. - this.addAbility( - new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutOnLibraryEffect( - new TargetCardInLibrary(new FilterBySubtypeCard(SubType.MERFOLK)), - true - ), - true - ) - ); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(filter), true), true + )); // Whenever another Merfolk you control enters, put a +1/+1 counter on Forerunner of the Heralds. - Ability ability = new EntersBattlefieldControlledTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filterAnotherMerfolk); - this.addAbility(ability); + this.addAbility(new EntersBattlefieldAllTriggeredAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()), filter2 + )); } private ForerunnerOfTheHeralds(final ForerunnerOfTheHeralds card) { diff --git a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheLegion.java b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheLegion.java index 7876f62aff6..fe59885e4d2 100644 --- a/Mage.Sets/src/mage/cards/f/ForerunnerOfTheLegion.java +++ b/Mage.Sets/src/mage/cards/f/ForerunnerOfTheLegion.java @@ -1,32 +1,35 @@ - package mage.cards.f; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.search.SearchLibraryPutOnLibraryEffect; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.FilterCard; import mage.filter.FilterPermanent; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCreaturePermanent; +import java.util.UUID; + /** - * * @author JayDi85 */ public final class ForerunnerOfTheLegion extends CardImpl { - private static final FilterPermanent filterAnotherVampire = new FilterPermanent(SubType.VAMPIRE, "another " + SubType.VAMPIRE.toString()); + private static final FilterCard filter = new FilterCard(SubType.VAMPIRE); + private static final FilterPermanent filter2 + = new FilterControlledPermanent(SubType.VAMPIRE, "another Vampire you control"); + static { - filterAnotherVampire.add(AnotherPredicate.instance); - filterAnotherVampire.add(TargetController.YOU.getControllerPredicate()); + filter2.add(AnotherPredicate.instance); } public ForerunnerOfTheLegion(UUID ownerId, CardSetInfo setInfo) { @@ -38,18 +41,12 @@ public final class ForerunnerOfTheLegion extends CardImpl { this.toughness = new MageInt(2); // When Forerunner of the Legion enters the battlefield, you may search your library for a Vampire card, reveal it, then shuffle your library and put that card on top of it. - this.addAbility( - new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutOnLibraryEffect( - new TargetCardInLibrary(new FilterBySubtypeCard(SubType.VAMPIRE)), - true - ), - true - ) - ); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutOnLibraryEffect(new TargetCardInLibrary(filter), true), true + )); // Whenever another Vampire you control enters, target creature gets +1/+1 until end of turn. - Ability ability = new EntersBattlefieldControlledTriggeredAbility(new BoostTargetEffect(1,1, Duration.EndOfTurn), filterAnotherVampire); + Ability ability = new EntersBattlefieldAllTriggeredAbility(new BoostTargetEffect(1, 1), filter2); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GaeasTouch.java b/Mage.Sets/src/mage/cards/g/GaeasTouch.java index c39ad5d322b..fd1a592b044 100644 --- a/Mage.Sets/src/mage/cards/g/GaeasTouch.java +++ b/Mage.Sets/src/mage/cards/g/GaeasTouch.java @@ -9,8 +9,12 @@ import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TimingRule; +import mage.constants.Zone; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import java.util.UUID; @@ -19,12 +23,7 @@ import java.util.UUID; */ public final class GaeasTouch extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Forest card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST); public GaeasTouch(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{G}{G}"); diff --git a/Mage.Sets/src/mage/cards/g/GlimpseTheCore.java b/Mage.Sets/src/mage/cards/g/GlimpseTheCore.java index 0445ebb6702..03c1269f250 100644 --- a/Mage.Sets/src/mage/cards/g/GlimpseTheCore.java +++ b/Mage.Sets/src/mage/cards/g/GlimpseTheCore.java @@ -8,7 +8,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetCardInYourGraveyard; @@ -19,7 +19,7 @@ import java.util.UUID; */ public final class GlimpseTheCore extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard(SubType.FOREST); + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST); private static final FilterCard filter2 = new FilterCard("Cave card from your graveyard"); static { diff --git a/Mage.Sets/src/mage/cards/g/GloryheathLynx.java b/Mage.Sets/src/mage/cards/g/GloryheathLynx.java index cf551f1ccbf..738cdfd068a 100644 --- a/Mage.Sets/src/mage/cards/g/GloryheathLynx.java +++ b/Mage.Sets/src/mage/cards/g/GloryheathLynx.java @@ -9,8 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -20,13 +19,6 @@ import java.util.UUID; */ public final class GloryheathLynx extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - public GloryheathLynx(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); @@ -40,7 +32,7 @@ public final class GloryheathLynx extends CardImpl { // Whenever this creature attacks while saddled, search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. this.addAbility(new AttacksWhileSaddledTriggeredAbility( - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true) + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true) )); // Saddle 2 diff --git a/Mage.Sets/src/mage/cards/g/GreenGoblinRevenant.java b/Mage.Sets/src/mage/cards/g/GreenGoblinRevenant.java new file mode 100644 index 00000000000..994bab4bcbc --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GreenGoblinRevenant.java @@ -0,0 +1,88 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.discard.DiscardControllerEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.Game; +import mage.watchers.common.DiscardedCardWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GreenGoblinRevenant extends CardImpl { + + public GreenGoblinRevenant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.GOBLIN); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.VILLAIN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Whenever Green Goblin attacks, discard a card. Then draw a card for each card you've discarded this turn. + Ability ability = new AttacksTriggeredAbility(new DiscardControllerEffect(1)); + ability.addEffect(new DrawCardSourceControllerEffect(GreenGoblinRevenantValue.instance).concatBy("Then")); + this.addAbility(ability.addHint(GreenGoblinRevenantValue.getHint()), new DiscardedCardWatcher()); + } + + private GreenGoblinRevenant(final GreenGoblinRevenant card) { + super(card); + } + + @Override + public GreenGoblinRevenant copy() { + return new GreenGoblinRevenant(this); + } +} + +enum GreenGoblinRevenantValue implements DynamicValue { + instance; + private static final Hint hint = new ValueHint("Cards you've discarded this turn", instance); + + public static Hint getHint() { + return hint; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return DiscardedCardWatcher.getDiscarded(sourceAbility.getControllerId(), game); + } + + @Override + public GreenGoblinRevenantValue copy() { + return instance; + } + + @Override + public String toString() { + return "1"; + } + + @Override + public String getMessage() { + return "card you've discarded this turn"; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GrixisPanorama.java b/Mage.Sets/src/mage/cards/g/GrixisPanorama.java index 56eb1fbc431..5a62f22285a 100644 --- a/Mage.Sets/src/mage/cards/g/GrixisPanorama.java +++ b/Mage.Sets/src/mage/cards/g/GrixisPanorama.java @@ -1,7 +1,5 @@ - package mage.cards.g; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; @@ -11,13 +9,16 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author North */ public final class GrixisPanorama extends CardImpl { @@ -25,7 +26,6 @@ public final class GrixisPanorama extends CardImpl { private static final FilterCard filter = new FilterCard("a basic Island, Swamp, or Mountain card"); static { - filter.add(CardType.LAND.getPredicate()); filter.add(SuperType.BASIC.getPredicate()); filter.add(Predicates.or( SubType.ISLAND.getPredicate(), @@ -34,7 +34,7 @@ public final class GrixisPanorama extends CardImpl { } public GrixisPanorama(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},""); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); this.addAbility(new ColorlessManaAbility()); TargetCardInLibrary target = new TargetCardInLibrary(filter); diff --git a/Mage.Sets/src/mage/cards/g/Groundskeeper.java b/Mage.Sets/src/mage/cards/g/Groundskeeper.java index b5b35ee5935..57fb607e64a 100644 --- a/Mage.Sets/src/mage/cards/g/Groundskeeper.java +++ b/Mage.Sets/src/mage/cards/g/Groundskeeper.java @@ -9,8 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInYourGraveyard; import java.util.UUID; @@ -20,8 +19,6 @@ import java.util.UUID; */ public final class Groundskeeper extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("basic land card from your graveyard"); - public Groundskeeper(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); this.subtype.add(SubType.HUMAN); @@ -31,7 +28,7 @@ public final class Groundskeeper extends CardImpl { // {1}{G}: Return target basic land card from your graveyard to your hand. Ability ability = new SimpleActivatedAbility(new ReturnFromGraveyardToHandTargetEffect(), new ManaCostsImpl<>("{1}{G}")); - ability.addTarget(new TargetCardInYourGraveyard(filter)); + ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_BASIC_LAND)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/h/HauntingVoyage.java b/Mage.Sets/src/mage/cards/h/HauntingVoyage.java index 345561f7f42..ee33c73254d 100644 --- a/Mage.Sets/src/mage/cards/h/HauntingVoyage.java +++ b/Mage.Sets/src/mage/cards/h/HauntingVoyage.java @@ -1,28 +1,26 @@ package mage.cards.h; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.condition.common.ForetoldCondition; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ChooseCreatureTypeEffect; import mage.abilities.keyword.ForetellAbility; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; +import mage.target.TargetCard; import mage.target.common.TargetCardInYourGraveyard; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class HauntingVoyage extends CardImpl { @@ -70,30 +68,15 @@ class HauntingVoyageEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); SubType chosenSubType = ChooseCreatureTypeEffect.getChosenCreatureType(source.getSourceId(), game); - if (controller != null && chosenSubType != null) { - Set cardsToBattlefield = new LinkedHashSet<>(); - if (!ForetoldCondition.instance.apply(game, source)) { - TargetCardInYourGraveyard target = new TargetCardInYourGraveyard(0, 2, new FilterBySubtypeCard(chosenSubType), true); - controller.chooseTarget(outcome, target, source, game); - for (UUID cardId : target.getTargets()) { - Card card = game.getCard(cardId); - if (card != null) { - cardsToBattlefield.add(card); - } - } - } else { - for (UUID cardId : controller.getGraveyard()) { - Card card = game.getCard(cardId); - if (card != null && card.hasSubtype(chosenSubType, game)) { - cardsToBattlefield.add(card); - } - } - } - if (!cardsToBattlefield.isEmpty()) { - controller.moveCards(cardsToBattlefield, Zone.BATTLEFIELD, source, game); - return true; - } + if (controller == null || chosenSubType == null) { + return false; } - return false; + FilterCard filter = new FilterCard(chosenSubType); + if (ForetoldCondition.instance.apply(game, source)) { + return controller.moveCards(controller.getGraveyard().getCards(filter, game), Zone.BATTLEFIELD, source, game); + } + TargetCard target = new TargetCardInYourGraveyard(0, 2, filter, true); + controller.chooseTarget(outcome, target, source, game); + return controller.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); } } diff --git a/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java b/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java index a88998a3287..0108ea30338 100644 --- a/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java +++ b/Mage.Sets/src/mage/cards/h/HonoredKnightCaptain.java @@ -13,7 +13,6 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBySubtypeCard; import mage.game.permanent.token.HumanSoldierToken; import mage.target.common.TargetCardInLibrary; @@ -24,7 +23,7 @@ import java.util.UUID; */ public final class HonoredKnightCaptain extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.EQUIPMENT); + private static final FilterCard filter = new FilterCard(SubType.EQUIPMENT); public HonoredKnightCaptain(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); diff --git a/Mage.Sets/src/mage/cards/i/ImperialHellkite.java b/Mage.Sets/src/mage/cards/i/ImperialHellkite.java index d896db14b09..5aae2539a15 100644 --- a/Mage.Sets/src/mage/cards/i/ImperialHellkite.java +++ b/Mage.Sets/src/mage/cards/i/ImperialHellkite.java @@ -1,11 +1,8 @@ - package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.MorphAbility; @@ -13,31 +10,34 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.FilterCard; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author fireshoes */ public final class ImperialHellkite extends CardImpl { - + + private static final FilterCard filter = new FilterCard(SubType.DRAGON); + public ImperialHellkite(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{5}{R}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{R}{R}"); this.subtype.add(SubType.DRAGON); this.power = new MageInt(6); this.toughness = new MageInt(6); // Flying this.addAbility(FlyingAbility.getInstance()); - + // Morph {6}{R}{R} this.addAbility(new MorphAbility(this, new ManaCostsImpl<>("{6}{R}{R}"))); - + // When Imperial Hellkite is turned face up, you may search your library for a Dragon card, reveal it, and put it into your hand. If you do, shuffle your library. - Effect effect = new SearchLibraryPutInHandEffect(new TargetCardInLibrary(0, 1, new FilterBySubtypeCard(SubType.DRAGON)), true); - effect.setText("you may search your library for a Dragon card, reveal it, put it into your hand, then shuffle"); - this.addAbility(new TurnedFaceUpSourceTriggeredAbility(effect)); + this.addAbility(new TurnedFaceUpSourceTriggeredAbility( + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), false, true + )); } private ImperialHellkite(final ImperialHellkite card) { diff --git a/Mage.Sets/src/mage/cards/i/InfernalCaretaker.java b/Mage.Sets/src/mage/cards/i/InfernalCaretaker.java index 550e2064979..e5144999d67 100644 --- a/Mage.Sets/src/mage/cards/i/InfernalCaretaker.java +++ b/Mage.Sets/src/mage/cards/i/InfernalCaretaker.java @@ -1,11 +1,8 @@ - package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.TurnedFaceUpSourceTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.ReturnToHandFromGraveyardAllEffect; import mage.abilities.keyword.MorphAbility; import mage.cards.CardImpl; @@ -13,18 +10,18 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBySubtypeCard; + +import java.util.UUID; /** - * * @author cg5 */ public final class InfernalCaretaker extends CardImpl { - private static FilterCard zombieCard = new FilterBySubtypeCard(SubType.ZOMBIE); - + private static FilterCard zombieCard = new FilterCard(SubType.ZOMBIE); + public InfernalCaretaker(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); this.subtype.add(SubType.HUMAN); this.subtype.add(SubType.CLERIC); this.power = new MageInt(2); @@ -32,11 +29,10 @@ public final class InfernalCaretaker extends CardImpl { // Morph {3}{B} this.addAbility(new MorphAbility(this, new ManaCostsImpl<>("{3}{B}"))); - + // When Infernal Caretaker is turned face up, return all Zombie cards from all graveyards to their owners' hands. - Effect effect = new ReturnToHandFromGraveyardAllEffect(zombieCard); - effect.setText("return all Zombie cards from all graveyards to their owners' hands"); - this.addAbility(new TurnedFaceUpSourceTriggeredAbility(effect)); + this.addAbility(new TurnedFaceUpSourceTriggeredAbility(new ReturnToHandFromGraveyardAllEffect(zombieCard) + .setText("return all Zombie cards from all graveyards to their owners' hands"))); } private InfernalCaretaker(final InfernalCaretaker card) { @@ -47,4 +43,4 @@ public final class InfernalCaretaker extends CardImpl { public InfernalCaretaker copy() { return new InfernalCaretaker(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/i/IronSpiderStarkUpgrade.java b/Mage.Sets/src/mage/cards/i/IronSpiderStarkUpgrade.java new file mode 100644 index 00000000000..1b97e9734d0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/i/IronSpiderStarkUpgrade.java @@ -0,0 +1,74 @@ +package mage.cards.i; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.RemoveCounterCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class IronSpiderStarkUpgrade extends CardImpl { + + private static final FilterPermanent filter + = new FilterControlledPermanent("creature and/or Vehicle you control"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.VEHICLE.getPredicate() + )); + } + + public IronSpiderStarkUpgrade(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // {T}: Put a +1/+1 counter on each artifact creature and/or Vehicle you control. + this.addAbility(new SimpleActivatedAbility( + new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter), new TapSourceCost() + )); + + // {2}, Remove two +1/+1 counters from among artifacts you control: Draw a card. + Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(1), new GenericManaCost(2)); + ability.addCost(new RemoveCounterCost(new TargetPermanent( + 1, 2, StaticFilters.FILTER_CONTROLLED_PERMANENT_ARTIFACT + ), CounterType.P1P1, 2).setText("remove two +1/+1 counters from among creatures you control")); + this.addAbility(ability); + } + + private IronSpiderStarkUpgrade(final IronSpiderStarkUpgrade card) { + super(card); + } + + @Override + public IronSpiderStarkUpgrade copy() { + return new IronSpiderStarkUpgrade(this); + } +} diff --git a/Mage.Sets/src/mage/cards/i/IymrithDesertDoom.java b/Mage.Sets/src/mage/cards/i/IymrithDesertDoom.java index 12e07f87554..3d551bc436c 100644 --- a/Mage.Sets/src/mage/cards/i/IymrithDesertDoom.java +++ b/Mage.Sets/src/mage/cards/i/IymrithDesertDoom.java @@ -1,27 +1,27 @@ package mage.cards.i; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.InvertCondition; import mage.abilities.condition.common.SourceTappedCondition; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.decorator.ConditionalContinuousEffect; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.DrawCardsEqualToDifferenceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; -import mage.abilities.keyword.WardAbility; -import mage.constants.*; import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.WardAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.game.Game; -import mage.players.Player; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; + +import java.util.UUID; /** - * * @author weirddan455 */ public final class IymrithDesertDoom extends CardImpl { @@ -40,13 +40,13 @@ public final class IymrithDesertDoom extends CardImpl { // Iymrith, Desert Doom has ward {4} as long as it's untapped. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( new GainAbilitySourceEffect(new WardAbility(new GenericManaCost(4)), Duration.WhileOnBattlefield), - SourceTappedCondition.UNTAPPED, - "{this} has ward {4} as long as it's untapped" + SourceTappedCondition.UNTAPPED, "{this} has ward {4} as long as it's untapped" ))); // Whenever Iymrith deals combat damage to a player, draw a card. Then if you have fewer than three cards in hand, draw cards equal to the difference. - Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new DrawCardSourceControllerEffect(1), false); - ability.addEffect(new IymrithDesertDoomEffect()); + Ability ability = new DealsCombatDamageToAPlayerTriggeredAbility(new DrawCardSourceControllerEffect(1)); + ability.addEffect(new DrawCardsEqualToDifferenceEffect(3) + .concatBy("Then if you have fewer than three cards in hand,")); this.addAbility(ability); } @@ -59,33 +59,3 @@ public final class IymrithDesertDoom extends CardImpl { return new IymrithDesertDoom(this); } } - -class IymrithDesertDoomEffect extends OneShotEffect { - - IymrithDesertDoomEffect() { - super(Outcome.DrawCard); - this.staticText = "Then if you have fewer than three cards in hand, draw cards equal to the difference"; - } - - private IymrithDesertDoomEffect(final IymrithDesertDoomEffect effect) { - super(effect); - } - - @Override - public IymrithDesertDoomEffect copy() { - return new IymrithDesertDoomEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - int handSize = controller.getHand().size(); - if (handSize < 3) { - controller.drawCards(3 - handSize, source, game); - return true; - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/j/JaceReawakened.java b/Mage.Sets/src/mage/cards/j/JaceReawakened.java index becdbb91226..b110e241803 100644 --- a/Mage.Sets/src/mage/cards/j/JaceReawakened.java +++ b/Mage.Sets/src/mage/cards/j/JaceReawakened.java @@ -1,14 +1,13 @@ package mage.cards.j; -import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.LoyaltyAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.CopyTargetStackObjectEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.DrawDiscardControllerEffect; import mage.abilities.effects.common.MayExileCardFromHandPlottedEffect; +import mage.abilities.effects.common.ruleModifying.CantCastDuringFirstThreeTurnsEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -18,7 +17,6 @@ import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; -import mage.players.Player; import mage.target.targetpointer.FixedTarget; import java.util.UUID; @@ -43,7 +41,7 @@ public final class JaceReawakened extends CardImpl { this.setStartingLoyalty(3); // You can't cast this spell during your first, second, or third turns of the game. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new CantCastJaceReawakenedEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new CantCastDuringFirstThreeTurnsEffect())); // +1: Draw a card, then discard a card. this.addAbility(new LoyaltyAbility(new DrawDiscardControllerEffect(1, 1), 1)); @@ -69,43 +67,6 @@ public final class JaceReawakened extends CardImpl { } } -/** - * Same as {@link mage.cards.s.SerraAvenger Serra Avenger} - */ -class CantCastJaceReawakenedEffect extends ContinuousRuleModifyingEffectImpl { - - CantCastJaceReawakenedEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - staticText = "You can't cast this spell during your first, second, or third turns of the game"; - } - - private CantCastJaceReawakenedEffect(final CantCastJaceReawakenedEffect effect) { - super(effect); - } - - @Override - public CantCastJaceReawakenedEffect copy() { - return new CantCastJaceReawakenedEffect(this); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.CAST_SPELL; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getSourceId().equals(source.getSourceId())) { - Player controller = game.getPlayer(source.getControllerId()); - // it can be cast on other players turn 1 - 3 if some effect let allow you to do this - if (controller != null && controller.getTurns() <= 3 && game.isActivePlayer(source.getControllerId())) { - return true; - } - } - return false; - } -} - class JaceReawakenedDelayedTriggeredAbility extends DelayedTriggeredAbility { JaceReawakenedDelayedTriggeredAbility() { diff --git a/Mage.Sets/src/mage/cards/j/JundPanorama.java b/Mage.Sets/src/mage/cards/j/JundPanorama.java index 7a84beb4ea0..17ee36d0611 100644 --- a/Mage.Sets/src/mage/cards/j/JundPanorama.java +++ b/Mage.Sets/src/mage/cards/j/JundPanorama.java @@ -2,7 +2,6 @@ package mage.cards.j; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; @@ -12,21 +11,23 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author BetaSteward_at_googlemail.com */ public final class JundPanorama extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Swamp, Mountain, or Forest card"); + private static final FilterCard filter = new FilterCard("a basic Swamp, Mountain, or Forest card"); static { - filter.add(CardType.LAND.getPredicate()); filter.add(SuperType.BASIC.getPredicate()); filter.add(Predicates.or( SubType.SWAMP.getPredicate(), @@ -35,7 +36,7 @@ public final class JundPanorama extends CardImpl { } public JundPanorama(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.LAND},null); + super(ownerId, setInfo, new CardType[]{CardType.LAND}, null); this.addAbility(new ColorlessManaAbility()); TargetCardInLibrary target = new TargetCardInLibrary(filter); Ability ability = new SimpleActivatedAbility(new SearchLibraryPutInPlayEffect(target, true), new GenericManaCost(1)); diff --git a/Mage.Sets/src/mage/cards/k/KaylasCommand.java b/Mage.Sets/src/mage/cards/k/KaylasCommand.java index f132c26673a..f6fc7fc9006 100644 --- a/Mage.Sets/src/mage/cards/k/KaylasCommand.java +++ b/Mage.Sets/src/mage/cards/k/KaylasCommand.java @@ -1,7 +1,5 @@ package mage.cards.k; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.effects.OneShotEffect; @@ -15,10 +13,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.SuperType; import mage.counters.CounterType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.permanent.token.Construct2Token; @@ -27,20 +23,13 @@ import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author weirddan455 */ public final class KaylasCommand extends CardImpl { - private static final FilterCard filter - = new FilterCard("a basic Plains card"); - - static { - filter.add(SubType.PLAINS.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); - } - public KaylasCommand(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}{W}"); @@ -55,7 +44,7 @@ public final class KaylasCommand extends CardImpl { this.getSpellAbility().addMode(new Mode(new KaylasCommandCounterEffect())); // * Search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. - this.getSpellAbility().addMode(new Mode(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true))); + this.getSpellAbility().addMode(new Mode(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true))); // * You gain 2 life and scry 2. Mode mode = new Mode(new GainLifeEffect(2)); diff --git a/Mage.Sets/src/mage/cards/k/KeeperOfTheAccord.java b/Mage.Sets/src/mage/cards/k/KeeperOfTheAccord.java index 6867dc7a039..cfc8ae722b7 100644 --- a/Mage.Sets/src/mage/cards/k/KeeperOfTheAccord.java +++ b/Mage.Sets/src/mage/cards/k/KeeperOfTheAccord.java @@ -10,9 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.constants.TargetController; -import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; import mage.game.Game; @@ -26,13 +24,6 @@ import java.util.UUID; */ public final class KeeperOfTheAccord extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - public KeeperOfTheAccord(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); @@ -48,7 +39,7 @@ public final class KeeperOfTheAccord extends CardImpl { // At the beginning of each opponent's end step, if that player controls more lands than you, you may search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle your library. this.addAbility(new BeginningOfEndStepTriggeredAbility( - TargetController.OPPONENT, new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true), true + TargetController.OPPONENT, new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true), true ).withInterveningIf(KeeperOfTheAccordCondition.LANDS)); } diff --git a/Mage.Sets/src/mage/cards/k/KnightOfTheWhiteOrchid.java b/Mage.Sets/src/mage/cards/k/KnightOfTheWhiteOrchid.java index aeac56a3b82..b45d6879bb0 100644 --- a/Mage.Sets/src/mage/cards/k/KnightOfTheWhiteOrchid.java +++ b/Mage.Sets/src/mage/cards/k/KnightOfTheWhiteOrchid.java @@ -12,7 +12,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBySubtypeCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -22,7 +21,7 @@ import java.util.UUID; */ public final class KnightOfTheWhiteOrchid extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.PLAINS); + private static final FilterCard filter = new FilterCard(SubType.PLAINS); private static final Condition condition = new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS); public KnightOfTheWhiteOrchid(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/k/KorCartographer.java b/Mage.Sets/src/mage/cards/k/KorCartographer.java index 7e8597e6836..305f2e19512 100644 --- a/Mage.Sets/src/mage/cards/k/KorCartographer.java +++ b/Mage.Sets/src/mage/cards/k/KorCartographer.java @@ -1,7 +1,5 @@ - package mage.cards.k; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; @@ -9,17 +7,20 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.FilterCard; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author North */ public final class KorCartographer extends CardImpl { + private static final FilterCard filter = new FilterCard(SubType.PLAINS); + public KorCartographer(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); this.subtype.add(SubType.KOR); this.subtype.add(SubType.SCOUT); @@ -27,7 +28,9 @@ public final class KorCartographer extends CardImpl { this.toughness = new MageInt(2); // When Kor Cartographer enters the battlefield, you may search your library for a Plains card, put it onto the battlefield tapped, then shuffle your library. - this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(new FilterBySubtypeCard(SubType.PLAINS)), true), true)); + this.addAbility(new EntersBattlefieldTriggeredAbility( + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true), true + )); } private KorCartographer(final KorCartographer card) { diff --git a/Mage.Sets/src/mage/cards/k/KothFireOfResistance.java b/Mage.Sets/src/mage/cards/k/KothFireOfResistance.java index 0261497d5a7..2fe11c98c2e 100644 --- a/Mage.Sets/src/mage/cards/k/KothFireOfResistance.java +++ b/Mage.Sets/src/mage/cards/k/KothFireOfResistance.java @@ -2,16 +2,20 @@ package mage.cards.k; import mage.abilities.Ability; import mage.abilities.LoyaltyAbility; +import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.GetEmblemEffect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.filter.common.FilterControlledPermanent; import mage.game.command.emblems.KothFireOfResistanceEmblem; import mage.target.common.TargetCardInLibrary; @@ -24,14 +28,11 @@ import java.util.UUID; */ public final class KothFireOfResistance extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Mountain card"); - private static final FilterControlledPermanent filter2 = new FilterControlledPermanent("Mountains you control"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.MOUNTAIN.getPredicate()); - filter2.add(SubType.MOUNTAIN.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.MOUNTAIN); + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount( + new FilterControlledPermanent(SubType.MOUNTAIN, "Mountains you control") + ); + private static final Hint hint = new ValueHint(xValue.getMessage(), xValue); public KothFireOfResistance(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{R}{R}"); @@ -44,10 +45,10 @@ public final class KothFireOfResistance extends CardImpl { this.addAbility(new LoyaltyAbility(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), 2)); // −3: Koth, Fire of Resistance deals damage to target creature equal to the number of Mountains you control. - Ability ability = new LoyaltyAbility(new DamageTargetEffect(new PermanentsOnBattlefieldCount(filter2)) + Ability ability = new LoyaltyAbility(new DamageTargetEffect(xValue) .setText("{this} deals damage to target creature equal to the number of Mountains you control"), -3); ability.addTarget(new TargetCreaturePermanent()); - this.addAbility(ability); + this.addAbility(ability.addHint(hint)); // −7: You get an emblem with "Whenever a Mountain you control enters, this emblem deals 4 damage to any target." this.addAbility(new LoyaltyAbility(new GetEmblemEffect(new KothFireOfResistanceEmblem()), -7)); diff --git a/Mage.Sets/src/mage/cards/k/KozilekTheGreatDistortion.java b/Mage.Sets/src/mage/cards/k/KozilekTheGreatDistortion.java index b8842f6f4b9..61396a8b402 100644 --- a/Mage.Sets/src/mage/cards/k/KozilekTheGreatDistortion.java +++ b/Mage.Sets/src/mage/cards/k/KozilekTheGreatDistortion.java @@ -7,9 +7,9 @@ import mage.abilities.condition.Condition; import mage.abilities.condition.common.CardsInHandCondition; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CastSourceTriggeredAbility; import mage.abilities.effects.common.CounterTargetEffect; +import mage.abilities.effects.common.DrawCardsEqualToDifferenceEffect; import mage.abilities.keyword.MenaceAbility; import mage.cards.Card; import mage.cards.CardImpl; @@ -44,7 +44,7 @@ public final class KozilekTheGreatDistortion extends CardImpl { this.toughness = new MageInt(12); // When you cast Kozilek, the Great Distortion, if you have fewer than seven cards in hand, draw cards equal to the difference. - this.addAbility(new CastSourceTriggeredAbility(new KozilekDrawEffect(), false).withInterveningIf(condition)); + this.addAbility(new CastSourceTriggeredAbility(new DrawCardsEqualToDifferenceEffect(7)).withInterveningIf(condition)); // Menace this.addAbility(new MenaceAbility(false)); @@ -65,33 +65,6 @@ public final class KozilekTheGreatDistortion extends CardImpl { } } -class KozilekDrawEffect extends OneShotEffect { - - KozilekDrawEffect() { - super(Outcome.DrawCard); - this.staticText = "draw cards equal to the difference"; - } - - private KozilekDrawEffect(final KozilekDrawEffect effect) { - super(effect); - } - - @Override - public KozilekDrawEffect copy() { - return new KozilekDrawEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - controller.drawCards(7 - controller.getHand().size(), source, game); - return true; - } - return false; - } -} - class KozilekDiscardCost extends CostImpl { public KozilekDiscardCost() { @@ -156,5 +129,4 @@ class KozilekDiscardCost extends CostImpl { public KozilekDiscardCost copy() { return new KozilekDiscardCost(this); } - } diff --git a/Mage.Sets/src/mage/cards/l/LodestoneBauble.java b/Mage.Sets/src/mage/cards/l/LodestoneBauble.java index 32f8886f014..129b19bce06 100644 --- a/Mage.Sets/src/mage/cards/l/LodestoneBauble.java +++ b/Mage.Sets/src/mage/cards/l/LodestoneBauble.java @@ -1,8 +1,6 @@ package mage.cards.l; -import java.util.List; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.SimpleActivatedAbility; @@ -14,23 +12,21 @@ import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DrawCardTargetEffect; -import mage.cards.Card; -import mage.cards.Cards; -import mage.cards.CardImpl; -import mage.cards.CardsImpl; -import mage.cards.CardSetInfo; +import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; import mage.target.TargetPlayer; import mage.target.common.TargetCardInGraveyard; import mage.target.targetpointer.FixedTarget; +import java.util.List; +import java.util.UUID; + /** - * * @author ThomasLerner, LevelX2 & L_J */ public final class LodestoneBauble extends CardImpl { @@ -61,7 +57,7 @@ public final class LodestoneBauble extends CardImpl { class LodestoneBaubleTarget extends TargetCardInGraveyard { public LodestoneBaubleTarget() { - super(0, 4, new FilterBasicLandCard("basic land cards from a player's graveyard")); + super(0, 4, StaticFilters.FILTER_CARD_BASIC_LANDS); } private LodestoneBaubleTarget(final LodestoneBaubleTarget target) { @@ -87,7 +83,7 @@ class LodestoneBaubleTarget extends TargetCardInGraveyard { } class LodestoneBaubleEffect extends OneShotEffect { - + LodestoneBaubleEffect() { super(Outcome.Detriment); this.staticText = "Put up to four target basic land cards from a player's graveyard on top of their library in any order"; diff --git a/Mage.Sets/src/mage/cards/l/LoyalWarhound.java b/Mage.Sets/src/mage/cards/l/LoyalWarhound.java index b4939d4e19e..c9dc6e69b41 100644 --- a/Mage.Sets/src/mage/cards/l/LoyalWarhound.java +++ b/Mage.Sets/src/mage/cards/l/LoyalWarhound.java @@ -10,9 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -22,7 +20,6 @@ import java.util.UUID; */ public final class LoyalWarhound extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard(SubType.PLAINS); private static final Condition condition = new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS); public LoyalWarhound(UUID ownerId, CardSetInfo setInfo) { @@ -38,7 +35,7 @@ public final class LoyalWarhound extends CardImpl { // When Loyal Warhound enters the battlefield, if an opponent controls more lands than you, // search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true) + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true) ).withInterveningIf(condition)); } diff --git a/Mage.Sets/src/mage/cards/m/MigrationPath.java b/Mage.Sets/src/mage/cards/m/MigrationPath.java index 88c4442a249..74d1de932d8 100644 --- a/Mage.Sets/src/mage/cards/m/MigrationPath.java +++ b/Mage.Sets/src/mage/cards/m/MigrationPath.java @@ -6,8 +6,7 @@ import mage.abilities.keyword.CyclingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -17,14 +16,12 @@ import java.util.UUID; */ public final class MigrationPath extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("basic land cards"); - public MigrationPath(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); // Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle your library. this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect( - new TargetCardInLibrary(0, 2, filter), true + new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS), true )); // Cycling {2} diff --git a/Mage.Sets/src/mage/cards/n/NeverwinterDryad.java b/Mage.Sets/src/mage/cards/n/NeverwinterDryad.java index d32f46b9461..d7b6ffd9207 100644 --- a/Mage.Sets/src/mage/cards/n/NeverwinterDryad.java +++ b/Mage.Sets/src/mage/cards/n/NeverwinterDryad.java @@ -10,8 +10,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -21,12 +21,7 @@ import java.util.UUID; */ public final class NeverwinterDryad extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Forest card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST); public NeverwinterDryad(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}"); diff --git a/Mage.Sets/src/mage/cards/n/NissaVastwoodSeer.java b/Mage.Sets/src/mage/cards/n/NissaVastwoodSeer.java index 14fac45f978..01b027d210d 100644 --- a/Mage.Sets/src/mage/cards/n/NissaVastwoodSeer.java +++ b/Mage.Sets/src/mage/cards/n/NissaVastwoodSeer.java @@ -12,6 +12,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.filter.common.FilterLandPermanent; import mage.target.common.TargetCardInLibrary; @@ -22,12 +23,7 @@ import java.util.UUID; */ public final class NissaVastwoodSeer extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Forest card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST); private static final Condition condition = new PermanentsOnTheBattlefieldCondition( new FilterLandPermanent("you control seven or more lands"), diff --git a/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java b/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java index a1bdc5302ca..0c206b20644 100644 --- a/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java +++ b/Mage.Sets/src/mage/cards/n/NissasPilgrimage.java @@ -1,7 +1,6 @@ package mage.cards.n; -import java.util.UUID; import mage.abilities.condition.common.SpellMasteryCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.search.SearchLibraryPutOntoBattlefieldTappedRestInHandEffect; @@ -9,22 +8,18 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class NissasPilgrimage extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Forest cards"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST, "basic Forest cards"); public NissasPilgrimage(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}"); diff --git a/Mage.Sets/src/mage/cards/n/NissasTriumph.java b/Mage.Sets/src/mage/cards/n/NissasTriumph.java index b331f7faaaf..9973eec8af1 100644 --- a/Mage.Sets/src/mage/cards/n/NissasTriumph.java +++ b/Mage.Sets/src/mage/cards/n/NissasTriumph.java @@ -8,10 +8,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; +import mage.filter.common.FilterBasicCard; import mage.filter.common.FilterControlledPlaneswalkerPermanent; import mage.target.common.TargetCardInLibrary; @@ -22,14 +22,9 @@ import java.util.UUID; */ public final class NissasTriumph extends CardImpl { - private static final FilterCard filter = new FilterCard("basic Forest cards"); + private static final FilterCard filter = new FilterBasicCard(SubType.FOREST, "basic Forest cards"); private static final FilterPermanent filter2 = new FilterControlledPlaneswalkerPermanent(SubType.NISSA); - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.FOREST.getPredicate()); - } - private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter2); public NissasTriumph(UUID ownerId, CardSetInfo setInfo) { diff --git a/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java b/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java index faff60d8cda..c4013547c4b 100644 --- a/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java +++ b/Mage.Sets/src/mage/cards/o/OldGrowthDryads.java @@ -1,10 +1,6 @@ package mage.cards.o; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; @@ -16,13 +12,16 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInLibrary; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + /** - * * @author TheElk801 */ public final class OldGrowthDryads extends CardImpl { @@ -70,7 +69,7 @@ class OldGrowthDryadsEffect extends OneShotEffect { for (UUID opponentId : game.getOpponents(source.getControllerId())) { Player opponent = game.getPlayer(opponentId); if (opponent != null && opponent.chooseUse(Outcome.PutLandInPlay, "Search your library for a basic land card and put it onto the battlefield tapped?", source, game)) { - TargetCardInLibrary target = new TargetCardInLibrary(new FilterBasicLandCard()); + TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND); if (opponent.searchLibrary(target, source, game)) { Card targetCard = opponent.getLibrary().getCard(target.getFirstTarget(), game); if (targetCard != null) { diff --git a/Mage.Sets/src/mage/cards/p/PathOfTheAnimist.java b/Mage.Sets/src/mage/cards/p/PathOfTheAnimist.java index 364c4813a13..7381e054b1c 100644 --- a/Mage.Sets/src/mage/cards/p/PathOfTheAnimist.java +++ b/Mage.Sets/src/mage/cards/p/PathOfTheAnimist.java @@ -8,7 +8,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.target.common.TargetCardInLibrary; /** diff --git a/Mage.Sets/src/mage/cards/p/PerilousLandscape.java b/Mage.Sets/src/mage/cards/p/PerilousLandscape.java index bddc03dd3ac..dbabbbf75ff 100644 --- a/Mage.Sets/src/mage/cards/p/PerilousLandscape.java +++ b/Mage.Sets/src/mage/cards/p/PerilousLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class PerilousLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Island, Mountain, or Plains card"); + private static final FilterCard filter = new FilterBasicCard("a basic Island, Mountain, or Plains card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/p/PilgrimOfTheAges.java b/Mage.Sets/src/mage/cards/p/PilgrimOfTheAges.java index d5253ad0410..161b6dfcf56 100644 --- a/Mage.Sets/src/mage/cards/p/PilgrimOfTheAges.java +++ b/Mage.Sets/src/mage/cards/p/PilgrimOfTheAges.java @@ -10,9 +10,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.constants.Zone; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -22,13 +21,6 @@ import java.util.UUID; */ public final class PilgrimOfTheAges extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - public PilgrimOfTheAges(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); @@ -38,7 +30,7 @@ public final class PilgrimOfTheAges extends CardImpl { // When Pilgrim of the Ages enters the battlefield, you may search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInHandEffect( - new TargetCardInLibrary(filter), true + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true ), true)); // {6}: Return Pilgrim of the Ages from your graveyard to your hand. diff --git a/Mage.Sets/src/mage/cards/p/Plasmancer.java b/Mage.Sets/src/mage/cards/p/Plasmancer.java index 3b34c120737..52e56ae77fc 100644 --- a/Mage.Sets/src/mage/cards/p/Plasmancer.java +++ b/Mage.Sets/src/mage/cards/p/Plasmancer.java @@ -8,8 +8,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -19,12 +19,7 @@ import java.util.UUID; */ public final class Plasmancer extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Swamp card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.SWAMP.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.SWAMP); public Plasmancer(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{B}{B}"); diff --git a/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java b/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java index 8ef4d238bb9..6436185825a 100644 --- a/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java +++ b/Mage.Sets/src/mage/cards/p/PulsarSquadronAce.java @@ -11,7 +11,6 @@ import mage.constants.PutCards; import mage.constants.SubType; import mage.counters.CounterType; import mage.filter.FilterCard; -import mage.filter.common.FilterBySubtypeCard; import java.util.UUID; @@ -20,7 +19,7 @@ import java.util.UUID; */ public final class PulsarSquadronAce extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.SPACECRAFT); + private static final FilterCard filter = new FilterCard(SubType.SPACECRAFT); public PulsarSquadronAce(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); diff --git a/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java b/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java index bfd78c1a564..60441b71c51 100644 --- a/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java +++ b/Mage.Sets/src/mage/cards/r/RagostDeftGastronaut.java @@ -14,12 +14,14 @@ import mage.abilities.effects.common.UntapSourceEffect; import mage.abilities.effects.common.continuous.AddCardSubtypeAllEffect; import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; import mage.abilities.token.FoodAbility; import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; +import mage.watchers.common.PlayerGainedLifeWatcher; import java.util.UUID; @@ -29,6 +31,7 @@ import java.util.UUID; public final class RagostDeftGastronaut extends CardImpl { private static final Condition condition = new YouGainedLifeCondition(); + private static final Hint hint = new ConditionHint(condition); public RagostDeftGastronaut(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{W}"); @@ -60,7 +63,7 @@ public final class RagostDeftGastronaut extends CardImpl { // At the beginning of each end step, if you gained life this turn, untap Ragost. this.addAbility(new BeginningOfEndStepTriggeredAbility( TargetController.ANY, new UntapSourceEffect(), false, condition - ).addHint(new ConditionHint(condition))); + ).addHint(hint), new PlayerGainedLifeWatcher()); } private RagostDeftGastronaut(final RagostDeftGastronaut card) { diff --git a/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java b/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java index ad16f667ac9..4a63e71307c 100644 --- a/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java +++ b/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java @@ -89,7 +89,7 @@ class RanarTheEverWatchfulCostReductionEffect extends CostModificationEffectImpl public boolean applies(Ability abilityToModify, Ability source, Game game) { ForetoldWatcher watcher = game.getState().getWatcher(ForetoldWatcher.class); return (watcher != null - && watcher.countNumberForetellThisTurn() == 0 + && watcher.getPlayerForetellCountThisTurn(source.getControllerId()) == 0 && abilityToModify.isControlledBy(source.getControllerId()) && abilityToModify instanceof ForetellAbility); } diff --git a/Mage.Sets/src/mage/cards/r/RoilingRegrowth.java b/Mage.Sets/src/mage/cards/r/RoilingRegrowth.java index 20f43c3368f..fc7f8d5a4c7 100644 --- a/Mage.Sets/src/mage/cards/r/RoilingRegrowth.java +++ b/Mage.Sets/src/mage/cards/r/RoilingRegrowth.java @@ -5,9 +5,7 @@ import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -17,8 +15,6 @@ import java.util.UUID; */ public final class RoilingRegrowth extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("basic land cards"); - public RoilingRegrowth(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); @@ -27,7 +23,7 @@ public final class RoilingRegrowth extends CardImpl { StaticFilters.FILTER_LAND, 1, null ).setText("Sacrifice a land.")); this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect( - new TargetCardInLibrary(0, 2, filter), true + new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS), true )); } diff --git a/Mage.Sets/src/mage/cards/s/SaguWildling.java b/Mage.Sets/src/mage/cards/s/SaguWildling.java index 8d08c6822c1..b81bfcdc405 100644 --- a/Mage.Sets/src/mage/cards/s/SaguWildling.java +++ b/Mage.Sets/src/mage/cards/s/SaguWildling.java @@ -1,27 +1,27 @@ package mage.cards.s; -import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; -import mage.cards.OmenCard; -import mage.constants.SubType; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardSetInfo; +import mage.cards.OmenCard; import mage.constants.CardType; -import mage.filter.common.FilterBasicLandCard; +import mage.constants.SubType; +import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author Jmlundeen */ public final class SaguWildling extends OmenCard { public SaguWildling(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{4}{G}", "Roost Seek", "{G}"); - + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, new CardType[]{CardType.SORCERY}, "{4}{G}", "Roost Seek", "{G}"); + this.subtype.add(SubType.DRAGON); this.power = new MageInt(3); this.toughness = new MageInt(3); @@ -34,7 +34,7 @@ public final class SaguWildling extends OmenCard { // Roost Seek // Search your library for a basic land card, reveal it, put it into your hand, then shuffle. - TargetCardInLibrary target = new TargetCardInLibrary(new FilterBasicLandCard()); + TargetCardInLibrary target = new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_LAND); this.getSpellCard().getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(target, true)); this.finalizeOmen(); } diff --git a/Mage.Sets/src/mage/cards/s/ScoutingHawk.java b/Mage.Sets/src/mage/cards/s/ScoutingHawk.java index e77d45c12f3..5917de178d7 100644 --- a/Mage.Sets/src/mage/cards/s/ScoutingHawk.java +++ b/Mage.Sets/src/mage/cards/s/ScoutingHawk.java @@ -10,8 +10,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; @@ -22,13 +20,6 @@ import java.util.UUID; */ public final class ScoutingHawk extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - private static final Condition condition = new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS); public ScoutingHawk(UUID ownerId, CardSetInfo setInfo) { @@ -43,7 +34,7 @@ public final class ScoutingHawk extends CardImpl { // Keen Sight — When Scouting Hawk enters the battlefield, if an opponent controls more lands than you, search your library for a basic Plains card, put it onto the battlefield tapped, then shuffle. this.addAbility(new EntersBattlefieldTriggeredAbility( - new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true) + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true) ).withInterveningIf(condition).withFlavorWord("Keen Sight")); } diff --git a/Mage.Sets/src/mage/cards/s/SeethingLandscape.java b/Mage.Sets/src/mage/cards/s/SeethingLandscape.java index 7f300662195..2d0fcb893f2 100644 --- a/Mage.Sets/src/mage/cards/s/SeethingLandscape.java +++ b/Mage.Sets/src/mage/cards/s/SeethingLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class SeethingLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Island, Swamp, or Mountain card"); + private static final FilterCard filter = new FilterBasicCard("a basic Island, Swamp, or Mountain card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/s/SerraAvenger.java b/Mage.Sets/src/mage/cards/s/SerraAvenger.java index 372ca879671..3a6885f576a 100644 --- a/Mage.Sets/src/mage/cards/s/SerraAvenger.java +++ b/Mage.Sets/src/mage/cards/s/SerraAvenger.java @@ -1,24 +1,17 @@ - package mage.cards.s; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.common.ruleModifying.CantCastDuringFirstThreeTurnsEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Duration; -import mage.constants.Outcome; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.players.Player; + +import java.util.UUID; /** @@ -27,21 +20,20 @@ import mage.players.Player; public final class SerraAvenger extends CardImpl { public SerraAvenger(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{W}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}{W}"); this.subtype.add(SubType.ANGEL); this.power = new MageInt(3); this.toughness = new MageInt(3); // You can't cast Serra Avenger during your first, second, or third turns of the game. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new CantCastSerraAvengerEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new CantCastDuringFirstThreeTurnsEffect())); // Flying this.addAbility(FlyingAbility.getInstance()); // Vigilance this.addAbility(VigilanceAbility.getInstance()); - } private SerraAvenger(final SerraAvenger card) { @@ -53,37 +45,3 @@ public final class SerraAvenger extends CardImpl { return new SerraAvenger(this); } } - -class CantCastSerraAvengerEffect extends ContinuousRuleModifyingEffectImpl { - - CantCastSerraAvengerEffect() { - super(Duration.WhileOnBattlefield, Outcome.Detriment); - staticText = "You can't cast this spell during your first, second, or third turns of the game"; - } - - private CantCastSerraAvengerEffect(final CantCastSerraAvengerEffect effect) { - super(effect); - } - - @Override - public CantCastSerraAvengerEffect copy() { - return new CantCastSerraAvengerEffect(this); - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.CAST_SPELL; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getSourceId().equals(source.getSourceId())) { - Player controller = game.getPlayer(source.getControllerId()); - // it can be cast on other players turn 1 - 3 if some effect let allow you to do this - if (controller != null && controller.getTurns() <= 3 && game.isActivePlayer(source.getControllerId())) { - return true; - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/s/ShatteredLandscape.java b/Mage.Sets/src/mage/cards/s/ShatteredLandscape.java index e4f4e1cbce3..3cf79f2a06b 100644 --- a/Mage.Sets/src/mage/cards/s/ShatteredLandscape.java +++ b/Mage.Sets/src/mage/cards/s/ShatteredLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class ShatteredLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Mountain, Plains, or Swamp card"); + private static final FilterCard filter = new FilterBasicCard("a basic Mountain, Plains, or Swamp card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/s/ShelteringLandscape.java b/Mage.Sets/src/mage/cards/s/ShelteringLandscape.java index b649e9d2abb..ad50704c9e7 100644 --- a/Mage.Sets/src/mage/cards/s/ShelteringLandscape.java +++ b/Mage.Sets/src/mage/cards/s/ShelteringLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class ShelteringLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Mountain, Forest, or Plains card"); + private static final FilterCard filter = new FilterBasicCard("a basic Mountain, Forest, or Plains card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/s/SpectacularSpiderMan.java b/Mage.Sets/src/mage/cards/s/SpectacularSpiderMan.java new file mode 100644 index 00000000000..82ca900a59a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpectacularSpiderMan.java @@ -0,0 +1,68 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.IndestructibleAbility; +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.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpectacularSpiderMan extends CardImpl { + + public SpectacularSpiderMan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // {1}: Spectacular Spider-Man gains flying until end of turn. + this.addAbility(new SimpleActivatedAbility( + new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), new GenericManaCost(1) + )); + + // {1}, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn. + Ability ability = new SimpleActivatedAbility(new GainAbilityControlledEffect( + HexproofAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("creatures you control gain hexproof"), new GenericManaCost(1)); + ability.addCost(new SacrificeSourceCost()); + ability.addEffect(new GainAbilityControlledEffect( + IndestructibleAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURES + ).setText("and indestructible until end of turn")); + this.addAbility(ability); + } + + private SpectacularSpiderMan(final SpectacularSpiderMan card) { + super(card); + } + + @Override + public SpectacularSpiderMan copy() { + return new SpectacularSpiderMan(this); + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpiderMan2099.java b/Mage.Sets/src/mage/cards/s/SpiderMan2099.java new file mode 100644 index 00000000000..badf925b6b5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpiderMan2099.java @@ -0,0 +1,129 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.ruleModifying.CantCastDuringFirstThreeTurnsEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.target.common.TargetAnyTarget; +import mage.watchers.Watcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SpiderMan2099 extends CardImpl { + + public SpiderMan2099(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.HERO); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // From the Future -- You can't cast Spider-Man 2099 during your first, second, or third turns of the game. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new CantCastDuringFirstThreeTurnsEffect("{this}") + ).withFlavorWord("From the Future")); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, Spider-Man 2099 deals damage equal to his power to any target. + Ability ability = new BeginningOfEndStepTriggeredAbility( + new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE) + .setText("{this} deals damage equal to his power to any target") + ).withInterveningIf(SpiderMan2099Condition.instance); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(ability.addHint(SpiderMan2099Condition.getHint()), new SpiderMan2099Watcher()); + } + + private SpiderMan2099(final SpiderMan2099 card) { + super(card); + } + + @Override + public SpiderMan2099 copy() { + return new SpiderMan2099(this); + } +} + +enum SpiderMan2099Condition implements Condition { + instance; + private static final Hint hint = new ConditionHint(instance); + + public static Hint getHint() { + return hint; + } + + @Override + public boolean apply(Game game, Ability source) { + return SpiderMan2099Watcher.checkPlayer(game, source); + } + + @Override + public String toString() { + return "you've played a land or cast a spell this turn from anywhere other than your hand"; + } +} + +class SpiderMan2099Watcher extends Watcher { + + private final Set set = new HashSet<>(); + + SpiderMan2099Watcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + switch (event.getType()) { + case LAND_PLAYED: + if (!Zone.HAND.match(event.getZone())) { + set.add(event.getPlayerId()); + } + break; + case SPELL_CAST: + Spell spell = game.getSpell(event.getTargetId()); + if (spell != null && !Zone.HAND.match(spell.getFromZone())) { + set.add(spell.getControllerId()); + } + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkPlayer(Game game, Ability source) { + return game + .getState() + .getWatcher(SpiderMan2099Watcher.class) + .set + .contains(source.getControllerId()); + } +} diff --git a/Mage.Sets/src/mage/cards/s/StoicFarmer.java b/Mage.Sets/src/mage/cards/s/StoicFarmer.java index 364f78fdefd..c2c90b44517 100644 --- a/Mage.Sets/src/mage/cards/s/StoicFarmer.java +++ b/Mage.Sets/src/mage/cards/s/StoicFarmer.java @@ -12,8 +12,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.target.common.TargetCardInLibrary; @@ -24,13 +22,6 @@ import java.util.UUID; */ public final class StoicFarmer extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Plains card"); - - static { - filter.add(SuperType.BASIC.getPredicate()); - filter.add(SubType.PLAINS.getPredicate()); - } - private static final Condition condition = new OpponentControlsMoreCondition(StaticFilters.FILTER_LANDS); public StoicFarmer(UUID ownerId, CardSetInfo setInfo) { @@ -44,8 +35,8 @@ public final class StoicFarmer extends CardImpl { // When Stoic Farmer enters the battlefield, search your library for a basic Plains card and reveal it. If an opponent controls more lands than you, put it onto the battlefield tapped. Otherwise, put it into your hand. Then shuffle your library. this.addAbility(new EntersBattlefieldTriggeredAbility( new ConditionalOneShotEffect( - new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(filter), true), - new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter), true), + new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true), + new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true), condition, "search your library for a basic Plains card and reveal it. " + "If an opponent controls more lands than you, put it onto the battlefield tapped. " + "Otherwise put it into your hand. Then shuffle" diff --git a/Mage.Sets/src/mage/cards/s/SunbladeSamurai.java b/Mage.Sets/src/mage/cards/s/SunbladeSamurai.java index 7224e1a3e64..658ea9f4b1b 100644 --- a/Mage.Sets/src/mage/cards/s/SunbladeSamurai.java +++ b/Mage.Sets/src/mage/cards/s/SunbladeSamurai.java @@ -10,8 +10,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.SuperType; import mage.filter.FilterCard; +import mage.filter.common.FilterBasicCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -21,12 +21,7 @@ import java.util.UUID; */ public final class SunbladeSamurai extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Plains card"); - - static { - filter.add(SubType.PLAINS.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); - } + private static final FilterCard filter = new FilterBasicCard(SubType.PLAINS); public SunbladeSamurai(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{W}"); diff --git a/Mage.Sets/src/mage/cards/t/TempleOfTheDragonQueen.java b/Mage.Sets/src/mage/cards/t/TempleOfTheDragonQueen.java index f680b5d10e8..cf986419a7e 100644 --- a/Mage.Sets/src/mage/cards/t/TempleOfTheDragonQueen.java +++ b/Mage.Sets/src/mage/cards/t/TempleOfTheDragonQueen.java @@ -15,7 +15,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; import mage.constants.Zone; -import mage.filter.common.FilterBySubtypeCard; +import mage.filter.FilterCard; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; @@ -54,7 +54,7 @@ public final class TempleOfTheDragonQueen extends CardImpl { class TempleOfTheDragonQueenEffect extends OneShotEffect { - private static final FilterBySubtypeCard filter = new FilterBySubtypeCard(SubType.DRAGON); + private static final FilterCard filter = new FilterCard(SubType.DRAGON); public TempleOfTheDragonQueenEffect() { super(Outcome.Tap); diff --git a/Mage.Sets/src/mage/cards/t/TheBirthOfMeletis.java b/Mage.Sets/src/mage/cards/t/TheBirthOfMeletis.java index a5c02de00f0..18ee073cf7d 100644 --- a/Mage.Sets/src/mage/cards/t/TheBirthOfMeletis.java +++ b/Mage.Sets/src/mage/cards/t/TheBirthOfMeletis.java @@ -9,8 +9,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SagaChapter; import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.game.permanent.token.ArtifactWallToken; import mage.target.common.TargetCardInLibrary; @@ -21,13 +20,6 @@ import java.util.UUID; */ public final class TheBirthOfMeletis extends CardImpl { - private static final FilterCard filter = new FilterCard("a basic Plains card"); - - static { - filter.add(SubType.PLAINS.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); - } - public TheBirthOfMeletis(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); @@ -39,7 +31,7 @@ public final class TheBirthOfMeletis extends CardImpl { // I — Search your library for a basic Plains card, reveal it, put it into your hand, then shuffle your library. sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_I, new SearchLibraryPutInHandEffect( - new TargetCardInLibrary(filter), true + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true ) ); diff --git a/Mage.Sets/src/mage/cards/t/TheRestorationOfEiganjo.java b/Mage.Sets/src/mage/cards/t/TheRestorationOfEiganjo.java index 6d8e353f4c7..3de02b60f26 100644 --- a/Mage.Sets/src/mage/cards/t/TheRestorationOfEiganjo.java +++ b/Mage.Sets/src/mage/cards/t/TheRestorationOfEiganjo.java @@ -10,8 +10,12 @@ import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SagaChapter; +import mage.constants.SubType; import mage.filter.FilterCard; +import mage.filter.StaticFilters; import mage.filter.common.FilterPermanentCard; import mage.filter.predicate.mageobject.ManaValuePredicate; import mage.target.common.TargetCardInLibrary; @@ -24,14 +28,10 @@ import java.util.UUID; */ public final class TheRestorationOfEiganjo extends CardImpl { - private static final FilterCard filter - = new FilterCard("a basic Plains card"); private static final FilterCard filter2 = new FilterPermanentCard("permanent card with mana value 2 or less from your graveyard"); static { - filter.add(SubType.PLAINS.getPredicate()); - filter.add(SuperType.BASIC.getPredicate()); filter2.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 3)); } @@ -47,7 +47,7 @@ public final class TheRestorationOfEiganjo extends CardImpl { // I - Search your library for a basic Plains card, reveal it, put it into your hand, then shuffle. sagaAbility.addChapterEffect( this, SagaChapter.CHAPTER_I, new SearchLibraryPutInHandEffect( - new TargetCardInLibrary(filter), true + new TargetCardInLibrary(StaticFilters.FILTER_CARD_BASIC_PLAINS), true ) ); diff --git a/Mage.Sets/src/mage/cards/t/TombstoneCareerCriminal.java b/Mage.Sets/src/mage/cards/t/TombstoneCareerCriminal.java index b38f4a67822..3b8066acb61 100644 --- a/Mage.Sets/src/mage/cards/t/TombstoneCareerCriminal.java +++ b/Mage.Sets/src/mage/cards/t/TombstoneCareerCriminal.java @@ -12,7 +12,6 @@ import mage.constants.CardType; import mage.constants.SubType; import mage.constants.SuperType; import mage.filter.FilterCard; -import mage.filter.common.FilterBySubtypeCard; import mage.target.common.TargetCardInYourGraveyard; import java.util.UUID; @@ -22,8 +21,8 @@ import java.util.UUID; */ public final class TombstoneCareerCriminal extends CardImpl { - private static final FilterCard filter = new FilterBySubtypeCard(SubType.VILLAIN); - private static final FilterCard filter2 = new FilterBySubtypeCard(SubType.VILLAIN, "Villain spells"); + private static final FilterCard filter = new FilterCard(SubType.VILLAIN); + private static final FilterCard filter2 = new FilterCard(SubType.VILLAIN, "Villain spells"); public TombstoneCareerCriminal(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); diff --git a/Mage.Sets/src/mage/cards/t/TranquilLandscape.java b/Mage.Sets/src/mage/cards/t/TranquilLandscape.java index 89b06c627ec..99c9104ff89 100644 --- a/Mage.Sets/src/mage/cards/t/TranquilLandscape.java +++ b/Mage.Sets/src/mage/cards/t/TranquilLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class TranquilLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Forest, Plains, or Island card"); + private static final FilterCard filter = new FilterBasicCard("a basic Forest, Plains, or Island card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/t/TwistedLandscape.java b/Mage.Sets/src/mage/cards/t/TwistedLandscape.java index 39c79522389..ce3ed038b08 100644 --- a/Mage.Sets/src/mage/cards/t/TwistedLandscape.java +++ b/Mage.Sets/src/mage/cards/t/TwistedLandscape.java @@ -14,7 +14,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; -import mage.filter.common.FilterBasicLandCard; +import mage.filter.common.FilterBasicCard; import mage.filter.predicate.Predicates; import mage.target.common.TargetCardInLibrary; @@ -25,7 +25,7 @@ import java.util.UUID; */ public final class TwistedLandscape extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("a basic Swamp, Mountain, or Forest card"); + private static final FilterCard filter = new FilterBasicCard("a basic Swamp, Mountain, or Forest card"); static { filter.add(Predicates.or( diff --git a/Mage.Sets/src/mage/cards/v/VastwoodSurge.java b/Mage.Sets/src/mage/cards/v/VastwoodSurge.java index 90138baa2e5..2b41ed826d8 100644 --- a/Mage.Sets/src/mage/cards/v/VastwoodSurge.java +++ b/Mage.Sets/src/mage/cards/v/VastwoodSurge.java @@ -9,9 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.counters.CounterType; -import mage.filter.FilterCard; import mage.filter.StaticFilters; -import mage.filter.common.FilterBasicLandCard; import mage.target.common.TargetCardInLibrary; import java.util.UUID; @@ -21,8 +19,6 @@ import java.util.UUID; */ public final class VastwoodSurge extends CardImpl { - private static final FilterCard filter = new FilterBasicLandCard("basic land cards"); - public VastwoodSurge(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{G}"); @@ -31,7 +27,7 @@ public final class VastwoodSurge extends CardImpl { // Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle your library. If this spell was kicked, put two +1/+1 counters on each creature you control. this.getSpellAbility().addEffect(new SearchLibraryPutInPlayEffect( - new TargetCardInLibrary(0, 2, filter), true + new TargetCardInLibrary(0, 2, StaticFilters.FILTER_CARD_BASIC_LANDS), true )); this.getSpellAbility().addEffect(new ConditionalOneShotEffect( new AddCountersAllEffect( diff --git a/Mage.Sets/src/mage/cards/v/VenomBlast.java b/Mage.Sets/src/mage/cards/v/VenomBlast.java index f9105491fb1..d9dd3aab89b 100644 --- a/Mage.Sets/src/mage/cards/v/VenomBlast.java +++ b/Mage.Sets/src/mage/cards/v/VenomBlast.java @@ -24,7 +24,7 @@ public final class VenomBlast extends CardImpl { this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2))); this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect() .setText("It deals damage equal to its power to up to one other target creature")); - this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent().setTargetTag(1)); this.getSpellAbility().addTarget(new TargetPermanent( 0, 1, StaticFilters.FILTER_ANOTHER_CREATURE_TARGET_2 ).setTargetTag(2)); diff --git a/Mage.Sets/src/mage/cards/w/WebWarriors.java b/Mage.Sets/src/mage/cards/w/WebWarriors.java new file mode 100644 index 00000000000..3e9c8a289c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WebWarriors.java @@ -0,0 +1,42 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WebWarriors extends CardImpl { + + public WebWarriors(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G/W}"); + + this.subtype.add(SubType.SPIDER); + this.subtype.add(SubType.HERO); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // When this creature enters, put a +1/+1 counter on each other creature you control. + this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect( + CounterType.P1P1.createInstance(), StaticFilters.FILTER_OTHER_CONTROLLED_CREATURE + ))); + } + + private WebWarriors(final WebWarriors card) { + super(card); + } + + @Override + public WebWarriors copy() { + return new WebWarriors(this); + } +} diff --git a/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java b/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java index 359e2562dc7..746e298113e 100644 --- a/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java +++ b/Mage.Sets/src/mage/sets/MarvelsSpiderMan.java @@ -4,15 +4,11 @@ import mage.cards.ExpansionSet; import mage.constants.Rarity; import mage.constants.SetType; -import java.util.Arrays; -import java.util.List; - /** * @author TheElk801 */ public final class MarvelsSpiderMan extends ExpansionSet { - private static final List unfinished = Arrays.asList("Electro's Bolt", "Spider-Islanders"); private static final MarvelsSpiderMan instance = new MarvelsSpiderMan(); public static MarvelsSpiderMan getInstance() { @@ -25,17 +21,26 @@ public final class MarvelsSpiderMan extends ExpansionSet { this.hasBasicLands = true; cards.add(new SetCardInfo("Angry Rabble", 75, Rarity.COMMON, mage.cards.a.AngryRabble.class)); + cards.add(new SetCardInfo("Anti-Venom, Horrifying Healer", 1, Rarity.MYTHIC, mage.cards.a.AntiVenomHorrifyingHealer.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Anti-Venom, Horrifying Healer", 244, Rarity.MYTHIC, mage.cards.a.AntiVenomHorrifyingHealer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aunt May", 3, Rarity.UNCOMMON, mage.cards.a.AuntMay.class)); cards.add(new SetCardInfo("Beetle, Legacy Criminal", 26, Rarity.COMMON, mage.cards.b.BeetleLegacyCriminal.class)); cards.add(new SetCardInfo("Daily Bugle Reporters", 6, Rarity.COMMON, mage.cards.d.DailyBugleReporters.class)); cards.add(new SetCardInfo("Doc Ock's Henchmen", 30, Rarity.COMMON, mage.cards.d.DocOcksHenchmen.class)); cards.add(new SetCardInfo("Doc Ock, Sinister Scientist", 29, Rarity.COMMON, mage.cards.d.DocOckSinisterScientist.class)); + cards.add(new SetCardInfo("Doctor Octopus, Master Planner", 128, Rarity.MYTHIC, mage.cards.d.DoctorOctopusMasterPlanner.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Doctor Octopus, Master Planner", 228, Rarity.MYTHIC, mage.cards.d.DoctorOctopusMasterPlanner.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Eerie Gravestone", 163, Rarity.COMMON, mage.cards.e.EerieGravestone.class)); cards.add(new SetCardInfo("Electro's Bolt", 77, Rarity.COMMON, mage.cards.e.ElectrosBolt.class)); cards.add(new SetCardInfo("Flying Octobot", 31, Rarity.UNCOMMON, mage.cards.f.FlyingOctobot.class)); + cards.add(new SetCardInfo("Forest", 193, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Forest", 198, Rarity.LAND, mage.cards.basiclands.Forest.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Green Goblin, Revenant", 130, Rarity.UNCOMMON, mage.cards.g.GreenGoblinRevenant.class)); cards.add(new SetCardInfo("Grow Extra Arms", 101, Rarity.COMMON, mage.cards.g.GrowExtraArms.class)); cards.add(new SetCardInfo("Guy in the Chair", 102, Rarity.COMMON, mage.cards.g.GuyInTheChair.class)); + cards.add(new SetCardInfo("Iron Spider, Stark Upgrade", 166, Rarity.RARE, mage.cards.i.IronSpiderStarkUpgrade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Iron Spider, Stark Upgrade", 279, Rarity.RARE, mage.cards.i.IronSpiderStarkUpgrade.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Island", 190, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Island", 195, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Kapow!", 103, Rarity.COMMON, mage.cards.k.Kapow.class)); cards.add(new SetCardInfo("Kraven's Cats", 104, Rarity.COMMON, mage.cards.k.KravensCats.class)); @@ -43,9 +48,12 @@ public final class MarvelsSpiderMan extends ExpansionSet { cards.add(new SetCardInfo("Masked Meower", 82, Rarity.COMMON, mage.cards.m.MaskedMeower.class)); cards.add(new SetCardInfo("Mechanical Mobster", 168, Rarity.COMMON, mage.cards.m.MechanicalMobster.class)); cards.add(new SetCardInfo("Merciless Enforcers", 58, Rarity.COMMON, mage.cards.m.MercilessEnforcers.class)); + cards.add(new SetCardInfo("Mountain", 192, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 197, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Origin of Spider-Man", 9, Rarity.RARE, mage.cards.o.OriginOfSpiderMan.class)); + cards.add(new SetCardInfo("Origin of Spider-Man", 218, Rarity.RARE, mage.cards.o.OriginOfSpiderMan.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Origin of Spider-Man", 9, Rarity.RARE, mage.cards.o.OriginOfSpiderMan.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Oscorp Research Team", 40, Rarity.COMMON, mage.cards.o.OscorpResearchTeam.class)); + cards.add(new SetCardInfo("Plains", 189, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Plains", 194, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Risky Research", 62, Rarity.COMMON, mage.cards.r.RiskyResearch.class)); cards.add(new SetCardInfo("Romantic Rendezvous", 86, Rarity.COMMON, mage.cards.r.RomanticRendezvous.class)); @@ -55,18 +63,25 @@ public final class MarvelsSpiderMan extends ExpansionSet { cards.add(new SetCardInfo("Selfless Police Captain", 12, Rarity.COMMON, mage.cards.s.SelflessPoliceCaptain.class)); cards.add(new SetCardInfo("Shock", 88, Rarity.COMMON, mage.cards.s.Shock.class)); cards.add(new SetCardInfo("Shocker, Unshakable", 89, Rarity.UNCOMMON, mage.cards.s.ShockerUnshakable.class)); + cards.add(new SetCardInfo("Spectacular Spider-Man", 14, Rarity.RARE, mage.cards.s.SpectacularSpiderMan.class)); cards.add(new SetCardInfo("Spectacular Tactics", 15, Rarity.COMMON, mage.cards.s.SpectacularTactics.class)); cards.add(new SetCardInfo("Spider-Bot", 173, Rarity.COMMON, mage.cards.s.SpiderBot.class)); cards.add(new SetCardInfo("Spider-Byte, Web Warden", 44, Rarity.UNCOMMON, mage.cards.s.SpiderByteWebWarden.class)); cards.add(new SetCardInfo("Spider-Gwen, Free Spirit", 90, Rarity.COMMON, mage.cards.s.SpiderGwenFreeSpirit.class)); - cards.add(new SetCardInfo("Spider-Ham, Peter Porker", 114, Rarity.RARE, mage.cards.s.SpiderHamPeterPorker.class)); + cards.add(new SetCardInfo("Spider-Ham, Peter Porker", 114, Rarity.RARE, mage.cards.s.SpiderHamPeterPorker.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Ham, Peter Porker", 201, Rarity.RARE, mage.cards.s.SpiderHamPeterPorker.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spider-Islanders", 91, Rarity.COMMON, mage.cards.s.SpiderIslanders.class)); - cards.add(new SetCardInfo("Spider-Man Noir", 67, Rarity.UNCOMMON, mage.cards.s.SpiderManNoir.class)); + cards.add(new SetCardInfo("Spider-Man 2099", 150, Rarity.RARE, mage.cards.s.SpiderMan2099.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Man 2099", 205, Rarity.RARE, mage.cards.s.SpiderMan2099.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Man 2099", 216, Rarity.RARE, mage.cards.s.SpiderMan2099.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Man Noir", 204, Rarity.UNCOMMON, mage.cards.s.SpiderManNoir.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Spider-Man Noir", 67, Rarity.UNCOMMON, mage.cards.s.SpiderManNoir.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Spider-Man, Brooklyn Visionary", 115, Rarity.COMMON, mage.cards.s.SpiderManBrooklynVisionary.class)); cards.add(new SetCardInfo("Spider-Man, Web-Slinger", 16, Rarity.COMMON, mage.cards.s.SpiderManWebSlinger.class)); cards.add(new SetCardInfo("Spider-Rex, Daring Dino", 116, Rarity.COMMON, mage.cards.s.SpiderRexDaringDino.class)); cards.add(new SetCardInfo("Starling, Aerial Ally", 18, Rarity.COMMON, mage.cards.s.StarlingAerialAlly.class)); cards.add(new SetCardInfo("Stegron the Dinosaur Man", 95, Rarity.COMMON, mage.cards.s.StegronTheDinosaurMan.class)); + cards.add(new SetCardInfo("Swamp", 191, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Swamp", 196, Rarity.LAND, mage.cards.basiclands.Swamp.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Taxi Driver", 97, Rarity.COMMON, mage.cards.t.TaxiDriver.class)); cards.add(new SetCardInfo("Thwip!", 20, Rarity.COMMON, mage.cards.t.Thwip.class)); @@ -75,9 +90,9 @@ public final class MarvelsSpiderMan extends ExpansionSet { cards.add(new SetCardInfo("Venom's Hunger", 73, Rarity.COMMON, mage.cards.v.VenomsHunger.class)); cards.add(new SetCardInfo("Venom, Evil Unleashed", 71, Rarity.COMMON, mage.cards.v.VenomEvilUnleashed.class)); cards.add(new SetCardInfo("Web Up", 21, Rarity.COMMON, mage.cards.w.WebUp.class)); + cards.add(new SetCardInfo("Web-Warriors", 159, Rarity.UNCOMMON, mage.cards.w.WebWarriors.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Web-Warriors", 203, Rarity.UNCOMMON, mage.cards.w.WebWarriors.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Whoosh!", 48, Rarity.COMMON, mage.cards.w.Whoosh.class)); cards.add(new SetCardInfo("Wild Pack Squad", 23, Rarity.COMMON, mage.cards.w.WildPackSquad.class)); - - cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); } } diff --git a/Mage.Sets/src/mage/sets/SpecialGuests.java b/Mage.Sets/src/mage/sets/SpecialGuests.java index 68a36bb95f7..ec2200fbe48 100644 --- a/Mage.Sets/src/mage/sets/SpecialGuests.java +++ b/Mage.Sets/src/mage/sets/SpecialGuests.java @@ -83,7 +83,7 @@ public final class SpecialGuests extends ExpansionSet { cards.add(new SetCardInfo("Lord of the Undead", 88, Rarity.MYTHIC, mage.cards.l.LordOfTheUndead.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lord of the Undead", 98, Rarity.MYTHIC, mage.cards.l.LordOfTheUndead.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Maddening Hex", 70, Rarity.MYTHIC, mage.cards.m.MaddeningHex.class)); - cards.add(new SetCardInfo("Magus of the Moon", 125, Rarity.RARE, mage.cards.m.MagusOfTheMoon.class, FULL_ART)); + cards.add(new SetCardInfo("Magus of the Moon", 125, Rarity.MYTHIC, mage.cards.m.MagusOfTheMoon.class, FULL_ART)); cards.add(new SetCardInfo("Malcolm, Keen-Eyed Navigator", 2, Rarity.UNCOMMON, mage.cards.m.MalcolmKeenEyedNavigator.class)); cards.add(new SetCardInfo("Mana Crypt", "17a", Rarity.MYTHIC, mage.cards.m.ManaCrypt.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mana Crypt", "17b", Rarity.MYTHIC, mage.cards.m.ManaCrypt.class, NON_FULL_USE_VARIOUS)); @@ -127,7 +127,7 @@ public final class SpecialGuests extends ExpansionSet { cards.add(new SetCardInfo("Show and Tell", 21, Rarity.MYTHIC, mage.cards.s.ShowAndTell.class)); cards.add(new SetCardInfo("Skysovereign, Consul Flagship", 103, Rarity.MYTHIC, mage.cards.s.SkysovereignConsulFlagship.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Skysovereign, Consul Flagship", 93, Rarity.MYTHIC, mage.cards.s.SkysovereignConsulFlagship.class, NON_FULL_USE_VARIOUS)); - cards.add(new SetCardInfo("Sliver Overlord", 128, Rarity.RARE, mage.cards.s.SliverOverlord.class, FULL_ART)); + cards.add(new SetCardInfo("Sliver Overlord", 128, Rarity.MYTHIC, mage.cards.s.SliverOverlord.class, FULL_ART)); cards.add(new SetCardInfo("Solitude", 44, Rarity.MYTHIC, mage.cards.s.Solitude.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Solitude", 49, Rarity.MYTHIC, mage.cards.s.Solitude.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Soul Warden", 65, Rarity.MYTHIC, mage.cards.s.SoulWarden.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ForetellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ForetellTest.java index d83cbb867ab..56d21bbca97 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ForetellTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ForetellTest.java @@ -2,6 +2,7 @@ package org.mage.test.cards.abilities.keywords; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.counters.CounterType; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -81,4 +82,243 @@ public class ForetellTest extends CardTestPlayerBase { assertExileCount(playerA, "Lightning Bolt", 1); // foretold card in exile assertPowerToughness(playerA, "Dream Devourer", 2, 3); // +2 power boost from trigger due to foretell of Lightning Bolt } + + + // Tests needed to check watcher scope issue (see issue #7493 and issue #13774) + + private static final String scornEffigy = "Scorn Effigy"; // {3} 2/3 foretell {0} + private static final String poisonCup = "Poison the Cup"; // {1}{B}{B} instant destroy target creature + // Foretell {1}{B}, if spell was foretold, scry 2 + private static final String flamespeaker = "Flamespeaker Adept"; // {2}{R} 2/3 + // Whenever you scry, gets +2/+0 and first strike until end of turn + private static final String chancemetElves = "Chance-Met Elves"; // {2}{G} 3/2 + // Whenever you scry, gets a +1/+1 counter, triggers once per turn + + private static final String cardE = "Elite Vanguard"; + private static final String cardD = "Devilthorn Fox"; + private static final String cardC = "Canopy Gorger"; + private static final String cardB = "Barbtooth Wurm"; + private static final String cardA = "Alaborn Trooper"; + + private void setupLibrariesEtc() { + // make a library of 5 cards, bottom : E D C B A : top + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, cardE); + addCard(Zone.LIBRARY, playerA, cardD); + addCard(Zone.LIBRARY, playerA, cardC); + addCard(Zone.LIBRARY, playerA, cardB); + addCard(Zone.LIBRARY, playerA, cardA); + removeAllCardsFromLibrary(playerB); + addCard(Zone.LIBRARY, playerB, cardE); + addCard(Zone.LIBRARY, playerB, cardD); + addCard(Zone.LIBRARY, playerB, cardC); + addCard(Zone.LIBRARY, playerB, cardB); + addCard(Zone.LIBRARY, playerB, cardA); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 5); + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 5); + addCard(Zone.BATTLEFIELD, playerA, flamespeaker); + addCard(Zone.BATTLEFIELD, playerB, chancemetElves); + addCard(Zone.HAND, playerA, scornEffigy); + } + + @Test + public void testForetellWatcherPlayerA() { + setupLibrariesEtc(); + addCard(Zone.HAND, playerA, poisonCup); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, scornEffigy); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell"); + checkExileCount("foretold in exile", 2, PhaseStep.PRECOMBAT_MAIN, playerA, poisonCup, 1); + // turn 3, draw card A + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell {1}{B}", chancemetElves); + // foretold, so scry 2 (cards B and C) + addTarget(playerA, cardB); // scrying B bottom (C remains on top) + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, scornEffigy, 2, 3); + assertGraveyardCount(playerA, poisonCup, 1); + assertGraveyardCount(playerB, chancemetElves, 1); + assertPowerToughness(playerA, flamespeaker, 4, 3); + } + + @Test + public void testForetellWatcherPlayerB() { + setupLibrariesEtc(); + addCard(Zone.HAND, playerB, poisonCup); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, scornEffigy); + // turn 2, draw card A + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Foretell"); + checkExileCount("foretold in exile", 2, PhaseStep.PRECOMBAT_MAIN, playerB, poisonCup, 1); + // turn 4, draw card B + activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, "Foretell {1}{B}", flamespeaker); + // foretold, so scry 2 (cards C and D) + addTarget(playerB, cardD); // scrying D bottom (C remains on top) + + setStrictChooseMode(true); + setStopAt(4, PhaseStep.END_TURN); + execute(); + + assertPowerToughness(playerA, scornEffigy, 2, 3); + assertGraveyardCount(playerB, poisonCup, 1); + assertGraveyardCount(playerA, flamespeaker, 1); + assertPowerToughness(playerB, chancemetElves, 4, 3); + } + + @Test + public void testRanar() { + skipInitShuffling(); + String ranar = "Ranar the Ever-Watchful"; // 2WU 2/3 Flying Vigilance + // The first card you foretell each turn costs {0} to foretell. + // Whenever one or more cards are put into exile from your hand or a spell or ability you control exiles + // one or more permanents from the battlefield, create a 1/1 white Spirit creature token with flying. + addCard(Zone.BATTLEFIELD, playerA, ranar); + addCard(Zone.BATTLEFIELD, playerA, "Sage of the Falls"); // may loot on creature ETB + addCard(Zone.HAND, playerA, poisonCup); + addCard(Zone.LIBRARY, playerA, scornEffigy); + addCard(Zone.HAND, playerA, "Wastes"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell"); // poison the cup + setChoice(playerA, true); // yes to loot + setChoice(playerA, "Wastes"); // discard + + checkExileCount("Poison the Cup foretold", 1, PhaseStep.BEGIN_COMBAT, playerA, poisonCup, 1); + checkHandCardCount("scorn effigy drawn", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, scornEffigy, 1); + checkPlayableAbility("can't foretell another for free", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell", false); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell"); // scorn effigy + setChoice(playerA, false); // no loot + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Spirit Token", 2); + assertExileCount(playerA, 2); + assertGraveyardCount(playerA, "Wastes", 1); + + } + + @Test + public void testCosmosCharger() { + addCard(Zone.BATTLEFIELD, playerA, "Cosmos Charger"); + // Foretelling cards from your hand costs {1} less and can be done on any player’s turn. + + addCard(Zone.HAND, playerA, scornEffigy); + addCard(Zone.BATTLEFIELD, playerA, "Wastes"); + + activateAbility(2, PhaseStep.UPKEEP, playerA, "Foretell"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + + assertExileCount(playerA, scornEffigy, 1); + } + + @Test + public void testAlrund() { + String alrund = "Alrund, God of the Cosmos"; + // Alrund gets +1/+1 for each card in your hand and each foretold card you own in exile. + + addCard(Zone.BATTLEFIELD, playerA, alrund); // 1/1 + addCard(Zone.HAND, playerA, scornEffigy); + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Cadaverous Bloom"); + // Exile a card from your hand: Add {B}{B} or {G}{G}. + + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Exile a card from your hand: Add {B}{B}"); + setChoice(playerA, "Lightning Bolt"); + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Foretell"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertHandCount(playerA, 0); + assertExileCount(playerA, scornEffigy, 1); + assertPowerToughness(playerA, alrund, 2, 2); + } + + private static final String valkyrie = "Ethereal Valkyrie"; // 4/4 flying + // Whenever this creature enters or attacks, draw a card, then exile a card from your hand face down. + // It becomes foretold. Its foretell cost is its mana cost reduced by {2}. + + @Test + public void testEtherealValkyrie() { + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + String saga = "Niko Defies Destiny"; + // I - You gain 2 life for each foretold card you own in exile. + // II - Add {W}{U}. Spend this mana only to foretell cards or cast spells that have foretell. + String crab = "Fortress Crab"; // 3U 1/6 + String puma = "Stonework Puma"; // {3} 2/2 + addCard(Zone.BATTLEFIELD, playerA, valkyrie); + addCard(Zone.HAND, playerA, saga); + addCard(Zone.HAND, playerA, crab); + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 5); + addCard(Zone.LIBRARY, playerA, "Wastes"); + addCard(Zone.LIBRARY, playerA, puma); + + attack(1, playerA, valkyrie, playerB); + addTarget(playerA, crab); // exile becomes foretold + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, saga); // gain 2 life + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + checkExileCount("crab foretold", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, crab, 1); + checkPlayableAbility("can't cast foretold same turn", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell", false); + + waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN); + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, puma); + + activateAbility(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 22); + assertLife(playerB, 16); + assertCounterCount(saga, CounterType.LORE, 2); + assertPowerToughness(playerA, crab, 1, 6); + assertPowerToughness(playerA, valkyrie, 4, 4); + assertPowerToughness(playerA, puma, 2, 2); + assertHandCount(playerA, 1); + assertHandCount(playerA, "Wastes", 1); + assertTappedCount("Tundra", true, 5); + } + + @Test + public void testForetoldNotForetell() { + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.LIBRARY, playerA, "Wastes"); + addCard(Zone.LIBRARY, playerA, "Darksteel Citadel"); + addCard(Zone.BATTLEFIELD, playerA, valkyrie); + addCard(Zone.BATTLEFIELD, playerA, "Dream Devourer"); + addCard(Zone.HAND, playerA, "Papercraft Decoy"); + + attack(1, playerA, valkyrie, playerB); + addTarget(playerA, "Papercraft Decoy"); // exile becomes foretold + + checkPT("Dream Devourer not boosted", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Dream Devourer", 0, 3); + checkPlayableAbility("Can't cast this turn", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Foretell", false); + checkHandCardCount("card drawn", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Darksteel Citadel", 1); + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Foretell"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 16); + assertPowerToughness(playerA, "Papercraft Decoy", 2, 1); + assertPowerToughness(playerA, "Dream Devourer", 0, 3); + assertHandCount(playerA, 2); + } + + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MayhemTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MayhemTest.java new file mode 100644 index 00000000000..79bfd41042b --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MayhemTest.java @@ -0,0 +1,89 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class MayhemTest extends CardTestPlayerBase { + + private static final String islanders = "Spider-Islanders"; + + @Test + public void testCastRegular() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.HAND, playerA, islanders); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, islanders); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, islanders, 1); + } + + private static final String imp = "Putrid Imp"; + + @Test + public void testCastDiscarded() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, imp); + addCard(Zone.HAND, playerA, islanders); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard"); + setChoice(playerA, islanders); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, islanders + " with Mayhem"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, islanders, 1); + } + + @Test + public void testCantCastGraveyard() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.GRAVEYARD, playerA, islanders); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, islanders + " with Mayhem"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + try { + execute(); + } catch (Throwable e) { + Assert.assertEquals( + "Should fail to be able to cast " + islanders + " with mayhem as it wasn't discarded this turn", + "Can't find ability to activate command: Cast " + islanders + " with Mayhem", e.getMessage() + ); + } + } + + @Test + public void testCantCastDiscardedPreviously() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, imp); + addCard(Zone.HAND, playerA, islanders); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard"); + setChoice(playerA, islanders); + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, islanders + " with Mayhem"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + try { + execute(); + } catch (Throwable e) { + Assert.assertEquals( + "Should fail to be able to cast " + islanders + " with mayhem as it wasn't discarded this turn", + "Can't find ability to activate command: Cast " + islanders + " with Mayhem", e.getMessage() + ); + } + } +} diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 2bd11e04d93..f7b6493ce98 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -5,10 +5,7 @@ import com.google.gson.Gson; import mage.MageObject; import mage.Mana; import mage.ObjectColor; -import mage.abilities.Ability; -import mage.abilities.AbilityImpl; -import mage.abilities.Mode; -import mage.abilities.TriggeredAbility; +import mage.abilities.*; import mage.abilities.common.*; import mage.abilities.condition.Condition; import mage.abilities.costs.Cost; @@ -2081,7 +2078,6 @@ public class VerifyCardDataTest { } } } - private void checkSubtypes(Card card, MtgJsonCard ref) { if (skipListHaveName(SKIP_LIST_SUBTYPE, card.getExpansionSetCode(), card.getName())) { return; @@ -2152,6 +2148,73 @@ public class VerifyCardDataTest { } } + // There are many cards that use the word "target" or "targets" in reference to spells/abilities that target rather than actually themselves having a target. + // Examples include Wall of Shadows, Psychic Battle, Coalition Honor Guard, Aboleth Spawn, Akroan Crusader, Grip of Chaos, and many more + Pattern singularTargetRegexPattern = Pattern.compile("\\b(? recursiveTargetAbilityCheck(ability, depth - 1)); + } + if (obj instanceof Collection) { + return ((Collection) obj).stream().anyMatch(x -> recursiveTargetObjectCheck(x, depth - 1)); + } + return false; + } + + boolean recursiveTargetEffectCheck(Effect effect, int depth) { + if (depth < 0) { + return false; + } + return Arrays.stream(effect.getClass().getDeclaredFields()) + .anyMatch(f -> { + f.setAccessible(true); + try { + return recursiveTargetObjectCheck(f.get(effect), depth); // Intentionally not decreasing depth here + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); // Should never happen due to setAccessible + } + }); + } + + boolean recursiveTargetAbilityCheck(Ability ability, int depth) { + if (depth < 0) { + return false; + } + Collection modes = ability.getModes().values(); + return modes.stream().flatMap(mode -> mode.getTargets().stream()).anyMatch(target -> !target.isNotTarget()) + || ability.getTargetAdjuster() != null + || modes.stream().flatMap(mode -> mode.getEffects().stream()).anyMatch(effect -> recursiveTargetEffectCheck(effect, depth - 1)); + } + private void checkMissingAbilities(Card card, MtgJsonCard ref) { if (skipListHaveName(SKIP_LIST_MISSING_ABILITIES, card.getExpansionSetCode(), card.getName())) { return; @@ -2315,57 +2378,51 @@ public class VerifyCardDataTest { } } + // special check: wrong targeted ability - // possible fixes: - // * on "must set withNotTarget(true)": - // - check card's ability constructors and fix missing withNotTarget(true) param/field - // - it's can be a keyword action (only mtg rules contains a target word), so add it to the targetedKeywords - // * on "must be targeted": - // - TODO: enable and research checkMissTargeted - too much errors with it (is it possible to use that checks?) - boolean checkMissNonTargeted = true; // must set withNotTarget(true) - boolean checkMissTargeted = false; // must be targeted - List targetedKeywords = Arrays.asList( - "target", - "enchant", - "equip", - "backup", - "modular", - "partner" - ); - // xmage card can contain rules text from both sides, so must search ref card for all sides too - String additionalName; - if (card instanceof CardWithSpellOption) { - // adventure/omen cards - additionalName = ((CardWithSpellOption) card).getSpellCard().getName(); - } else if (card.isTransformable() && !card.isNightCard()) { - additionalName = card.getSecondCardFace().getName(); - } else { - additionalName = null; - } - if (additionalName != null) { - MtgJsonCard additionalRef = MtgJsonService.cardFromSet(card.getExpansionSetCode(), additionalName, card.getCardNumber()); - if (additionalRef == null) { - // how-to fix: add new card type processing for an additionalName searching above - fail(card, "abilities", "can't find second side info for target check"); - } else { - if (additionalRef.text != null && !additionalRef.text.isEmpty()) { - refLowerText += "\r\n" + additionalRef.text.toLowerCase(Locale.ENGLISH); + // Checks that no ability targets use withNotTarget (use OneShotNonTargetEffect if it's a choose effect) + // Checks that, if the text contains the word target, the ability does have a target. + // - In cases involving a target in a reflexive trigger or token or other complex situation, it assumes that it's fine + // - There are two versions of this complexity check, either can trigger: one on card text, one that uses Java reflection to inspect the ability's effects. + String[] excludedCards = {"Lodestone Bauble", // Needs to choose a player before targets are selected + "Blink", // Current XMage code does not correctly support non-consecutive chapter effects, duplicates effects as a workaround + "Artifact Ward"}; // This card is just implemented wrong, but would need significant work to fix + if (Arrays.stream(excludedCards).noneMatch(x -> x.equals(ref.name))) { + for (Ability ability : card.getAbilities()) { + boolean foundNotTarget = ability.getModes().values().stream() + .flatMap(mode -> mode.getTargets().stream()).anyMatch(Target::isNotTarget); + if (foundNotTarget) { + fail(card, "abilities", "notTarget should not be used as ability target, should be inside ability effect"); + } + String abilityText = ability.getRule().toLowerCase(Locale.ENGLISH); + boolean needTargetedAbility = singularTargetRegexPattern.matcher(abilityText).find() || pluralTargetsRegexPattern.matcher(abilityText).find() || targetKeywordRegexPattern.matcher(abilityText).find(); + boolean recursiveAbilityText = indirectTriggerTargetRegexPattern.matcher(abilityText).find() || quotedTargetRegexPattern.matcher(abilityText).find(); + + boolean foundTargetedAbility = recursiveTargetAbilityCheck(ability, 0); + boolean recursiveAbility = recursiveTargetAbilityCheck(ability, 4); + + if (needTargetedAbility && !(foundTargetedAbility || recursiveAbilityText || recursiveAbility) + && card.getAbilities().stream().noneMatch(x -> x instanceof LevelUpAbility)) { // Targeting Level Up abilities' text is put in the power-toughness setting effect + fail(card, "abilities", "wrong target settings (must be targeted, but is not):" + ability.getClass().getSimpleName()); + } + if (!needTargetedAbility && foundTargetedAbility + && !(ability instanceof SpellAbility && abilityText.equals("") && card.getSubtype().contains(SubType.AURA)) // Auras' SpellAbility targets, not the EnchantAbility + && !(ability instanceof SpellAbility && (recursiveTargetAbilityCheck(card.getSpellAbility(), 0))) // SurgeAbility is a modified copy of the main SpellAbility, so it targets + && !(ability instanceof SpellTransformedAbility)) { // DisturbAbility targets if the backside aura targets + fail(card, "abilities", "wrong target settings (targeted ability found but no target in text):" + ability.getClass().getSimpleName()); } } - } - - boolean needTargetedAbility = targetedKeywords.stream().anyMatch(refLowerText::contains); - boolean foundTargetedAbility = card.getAbilities() - .stream() - .map(Ability::getTargets) - .flatMap(Collection::stream) - .anyMatch(target -> !target.isNotTarget()); - boolean foundProblem = needTargetedAbility != foundTargetedAbility; - if (checkMissTargeted && needTargetedAbility && foundProblem) { - fail(card, "abilities", "wrong target settings (must be targeted, but it not)"); - } - if (checkMissNonTargeted && !needTargetedAbility && foundProblem) { - fail(card, "abilities", "wrong target settings (must set withNotTarget(true), but it not)"); + // Also check that the reference text and the final ability text have the same number of "target" + String preparedRefText = refLowerText.replaceAll("\\([^)]+\\)", ""); // Remove reminder text + int refTargetCount = (preparedRefText.length() - preparedRefText.replace("target", "").length()); + String preparedRuleText = cardLowerText.replaceAll("\\([^)]+\\)", ""); + if (!ref.subtypes.contains("Adventure") && !ref.subtypes.contains("Omen")) { + preparedRuleText = preparedRuleText.replaceAll("^(adventure|omen).*", ""); + } + int cardTargetCount = (preparedRuleText.length() - preparedRuleText.replace("target", "").length()); + if (refTargetCount != cardTargetCount) { + fail(card, "abilities", "target count text discrepancy: " + (refTargetCount / 6) + " in reference but " + (cardTargetCount / 6) + " in card."); + } } // special check: missing or wrong ability/effect rules hint @@ -3064,6 +3121,11 @@ public class VerifyCardDataTest { return; } + // Spacecraft are ignored as they shouldn't have a printed power/toughness but they do in the data + if (ref.subtypes.contains("Spacecraft")) { + return; + } + if (!eqPT(card.getPower().toString(), ref.power) || !eqPT(card.getToughness().toString(), ref.toughness)) { String pt = card.getPower() + "/" + card.getToughness(); String expected = ref.power + '/' + ref.toughness; diff --git a/Mage/src/main/java/mage/abilities/common/ForetellSourceControllerTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ForetellSourceControllerTriggeredAbility.java index 975f5b725ec..81672a10fe9 100644 --- a/Mage/src/main/java/mage/abilities/common/ForetellSourceControllerTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ForetellSourceControllerTriggeredAbility.java @@ -7,6 +7,7 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.players.Player; +import mage.watchers.common.ForetoldWatcher; /** * @author jeffwadsworth @@ -16,6 +17,7 @@ public class ForetellSourceControllerTriggeredAbility extends TriggeredAbilityIm public ForetellSourceControllerTriggeredAbility(Effect effect) { super(Zone.BATTLEFIELD, effect, false); setTriggerPhrase("Whenever you foretell a card, "); + addWatcher(new ForetoldWatcher()); } protected ForetellSourceControllerTriggeredAbility(final ForetellSourceControllerTriggeredAbility ability) { @@ -24,16 +26,14 @@ public class ForetellSourceControllerTriggeredAbility extends TriggeredAbilityIm @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.FORETELL; + return event.getType() == GameEvent.EventType.CARD_FORETOLD; } @Override public boolean checkTrigger(GameEvent event, Game game) { Card card = game.getCard(event.getTargetId()); Player player = game.getPlayer(event.getPlayerId()); - return (card != null - && player != null - && isControlledBy(player.getId())); + return event.getFlag() && card != null && player != null && isControlledBy(player.getId()); } @Override diff --git a/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java b/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java index c0e63fc8663..9e3159b298f 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/ForetoldCondition.java @@ -17,7 +17,7 @@ public enum ForetoldCondition implements Condition { public boolean apply(Game game, Ability source) { ForetoldWatcher watcher = game.getState().getWatcher(ForetoldWatcher.class); if (watcher != null) { - return watcher.cardWasForetold(source.getSourceId()); + return watcher.checkForetold(source.getSourceId(), game); } return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/DrawCardsEqualToDifferenceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DrawCardsEqualToDifferenceEffect.java new file mode 100644 index 00000000000..25c23069aef --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/DrawCardsEqualToDifferenceEffect.java @@ -0,0 +1,37 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; + +/** + * @author TheElk801 + */ +public class DrawCardsEqualToDifferenceEffect extends OneShotEffect { + + private final int amount; + + public DrawCardsEqualToDifferenceEffect(int amount) { + super(Outcome.Benefit); + this.amount = amount; + staticText = "draw cards equal to the difference"; + } + + private DrawCardsEqualToDifferenceEffect(final DrawCardsEqualToDifferenceEffect effect) { + super(effect); + this.amount = effect.amount; + } + + @Override + public DrawCardsEqualToDifferenceEffect copy() { + return new DrawCardsEqualToDifferenceEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + return player != null && player.drawCards(Math.max(amount - player.getHand().size(), 0), source, game) > 0; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CantCastDuringFirstThreeTurnsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CantCastDuringFirstThreeTurnsEffect.java new file mode 100644 index 00000000000..26e131a92a0 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CantCastDuringFirstThreeTurnsEffect.java @@ -0,0 +1,47 @@ +package mage.abilities.effects.common.ruleModifying; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +/** + * @author TheElk801 + */ +public class CantCastDuringFirstThreeTurnsEffect extends ContinuousRuleModifyingEffectImpl { + + public CantCastDuringFirstThreeTurnsEffect() { + this("this spell"); + } + + public CantCastDuringFirstThreeTurnsEffect(String selfName) { + super(Duration.WhileOnBattlefield, Outcome.Detriment); + staticText = "you can't cast " + selfName + " during your first, second, or third turns of the game"; + } + + private CantCastDuringFirstThreeTurnsEffect(final CantCastDuringFirstThreeTurnsEffect effect) { + super(effect); + } + + @Override + public CantCastDuringFirstThreeTurnsEffect copy() { + return new CantCastDuringFirstThreeTurnsEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.CAST_SPELL; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!event.getSourceId().equals(source.getSourceId())) { + return false; + } + Player controller = game.getPlayer(source.getControllerId()); + return controller != null && controller.getTurns() <= 3 && game.isActivePlayer(source.getControllerId()); + } +} diff --git a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java index d094a49f537..421a25858d3 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ForetellAbility.java @@ -11,11 +11,15 @@ import mage.abilities.costs.Costs; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.*; import mage.constants.*; +import mage.filter.common.FilterNonlandCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; import mage.game.ExileZone; import mage.game.Game; import mage.game.events.GameEvent; @@ -90,132 +94,236 @@ public class ForetellAbility extends SpecialAction { return " foretells a card from hand"; } - static class ForetellExileEffect extends OneShotEffect { - - private final Card card; - String foretellCost; - String foretellSplitCost; - - ForetellExileEffect(Card card, String foretellCost, String foretellSplitCost) { - super(Outcome.Neutral); - this.card = card; - this.foretellCost = foretellCost; - this.foretellSplitCost = foretellSplitCost; - } - - protected ForetellExileEffect(final ForetellExileEffect effect) { - super(effect); - this.card = effect.card; - this.foretellCost = effect.foretellCost; - this.foretellSplitCost = effect.foretellSplitCost; - } - - @Override - public ForetellExileEffect copy() { - return new ForetellExileEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null - && card != null) { - - // get main card id - UUID mainCardId = card.getMainCard().getId(); - - // retrieve the exileId of the foretold card - UUID exileId = CardUtil.getExileZoneId(mainCardId.toString() + "foretellAbility", game); - - // foretell turn number shows up on exile window - ExileTargetEffect effect = new ExileTargetEffect(exileId, " Foretell Turn Number: " + game.getTurnNum()); - - // remember turn number it was cast - game.getState().setValue(mainCardId.toString() + "Foretell Turn Number", game.getTurnNum()); - - // remember the foretell cost - game.getState().setValue(mainCardId.toString() + "Foretell Cost", foretellCost); - game.getState().setValue(mainCardId.toString() + "Foretell Split Cost", foretellSplitCost); - - // exile the card face-down - effect.setWithName(false); - effect.setTargetPointer(new FixedTarget(card.getId(), game)); - effect.apply(game, source); - card.setFaceDown(true, game); - game.addEffect(new ForetellAddCostEffect(new MageObjectReference(card, game)), source); - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.FORETELL, card.getId(), null, source.getControllerId())); - return true; - } - return false; - } + public static boolean isCardInForetell(Card card, Game game) { + // searching ForetellCostAbility - it adds for foretelled cards only after exile + return card.getAbilities(game).containsClass(ForetellCostAbility.class); } - static class ForetellLookAtCardEffect extends AsThoughEffectImpl { + public static ContinuousEffect makeAddForetellEffect() { + return new ForetellAddAbilityEffect(); + } - ForetellLookAtCardEffect() { - super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.AIDontUseIt); + /** + * For use in apply() method of OneShotEffect + * Exile the target card. It becomes foretold. + * Its foretell cost is its mana cost reduced by [amountToReduceCost] + */ + public static boolean doExileBecomesForetold(Card card, Game game, Ability source, int amountToReduceCost) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; } - protected ForetellLookAtCardEffect(final ForetellLookAtCardEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public ForetellLookAtCardEffect copy() { - return new ForetellLookAtCardEffect(this); - } - - @Override - public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (affectedControllerId.equals(source.getControllerId())) { - Card card = game.getCard(objectId); - if (card != null) { - MageObject sourceObject = game.getObject(source); - if (sourceObject == null) { - return false; - } - UUID mainCardId = card.getMainCard().getId(); - UUID exileId = CardUtil.getExileZoneId(mainCardId.toString() + "foretellAbility", game); - ExileZone exile = game.getExile().getExileZone(exileId); - return exile != null - && exile.contains(mainCardId); + // process Split, MDFC, and Adventure cards first + // note that 'Foretell Cost' refers to the main card (left) and 'Foretell Split Cost' refers to the (right) card if it exists + ForetellAbility foretellAbility = null; + if (card instanceof SplitCard) { + String leftHalfCost = CardUtil.reduceCost(((SplitCard) card).getLeftHalfCard().getManaCost(), amountToReduceCost).getText(); + String rightHalfCost = CardUtil.reduceCost(((SplitCard) card).getRightHalfCard().getManaCost(), amountToReduceCost).getText(); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Cost", leftHalfCost); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Split Cost", rightHalfCost); + foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); + } else if (card instanceof ModalDoubleFacedCard) { + ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); + if (!leftHalfCard.isLand(game)) { // Only MDFC cards with a left side a land have a land on the right side too + String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), amountToReduceCost).getText(); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Cost", leftHalfCost); + ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + if (rightHalfCard.isLand(game)) { + foretellAbility = new ForetellAbility(card, leftHalfCost); + } else { + String rightHalfCost = CardUtil.reduceCost(rightHalfCard.getManaCost(), amountToReduceCost).getText(); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Split Cost", rightHalfCost); + foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); } } - return false; + } else if (card instanceof CardWithSpellOption) { + String creatureCost = CardUtil.reduceCost(card.getMainCard().getManaCost(), amountToReduceCost).getText(); + String spellCost = CardUtil.reduceCost(((CardWithSpellOption) card).getSpellCard().getManaCost(), amountToReduceCost).getText(); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Cost", creatureCost); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Split Cost", spellCost); + foretellAbility = new ForetellAbility(card, creatureCost, spellCost); + } else if (!card.isLand(game)) { + // normal card + String costText = CardUtil.reduceCost(card.getManaCost(), amountToReduceCost).getText(); + game.getState().setValue(card.getId().toString() + "Foretell Cost", costText); + foretellAbility = new ForetellAbility(card, costText); } + + // All card types (including lands) must be exiled + UUID exileId = CardUtil.getExileZoneId(card.getMainCard().getId().toString() + "foretellAbility", game); + controller.moveCardsToExile(card, source, game, false, exileId, " Foretell Turn Number: " + game.getTurnNum()); + card.setFaceDown(true, game); + + // all done pre-processing so stick the foretell cost effect onto the main card + // note that the card is not foretell'd into exile, it is put into exile and made foretold + // If the card is a non-land, it will not be exiled. + if (foretellAbility != null) { + // copy source and use it for the foretold effect on the exiled card + // bug #8673 + Ability copiedSource = source.copy(); + copiedSource.newId(); + copiedSource.setSourceId(card.getId()); + game.getState().setValue(card.getMainCard().getId().toString() + "Foretell Turn Number", game.getTurnNum()); + foretellAbility.setSourceId(card.getId()); + foretellAbility.setControllerId(card.getOwnerId()); + game.getState().addOtherAbility(card, foretellAbility); + foretellAbility.activate(game, true); + game.addEffect(new ForetellAddCostEffect(new MageObjectReference(card, game)), copiedSource); + game.fireEvent(new GameEvent(GameEvent.EventType.CARD_FORETOLD, card.getId(), copiedSource, copiedSource.getControllerId(), 0, false)); + } + return true; } - public static class ForetellAddCostEffect extends ContinuousEffectImpl { +} - private final MageObjectReference mor; +class ForetellExileEffect extends OneShotEffect { - public ForetellAddCostEffect(MageObjectReference mor) { - super(Duration.EndOfGame, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); - this.mor = mor; - staticText = "Foretold card"; + private final Card card; + String foretellCost; + String foretellSplitCost; + + ForetellExileEffect(Card card, String foretellCost, String foretellSplitCost) { + super(Outcome.Neutral); + this.card = card; + this.foretellCost = foretellCost; + this.foretellSplitCost = foretellSplitCost; + } + + private ForetellExileEffect(final ForetellExileEffect effect) { + super(effect); + this.card = effect.card; + this.foretellCost = effect.foretellCost; + this.foretellSplitCost = effect.foretellSplitCost; + } + + @Override + public ForetellExileEffect copy() { + return new ForetellExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null + && card != null) { + + // get main card id + UUID mainCardId = card.getMainCard().getId(); + + // retrieve the exileId of the foretold card + UUID exileId = CardUtil.getExileZoneId(mainCardId.toString() + "foretellAbility", game); + + // foretell turn number shows up on exile window + ExileTargetEffect effect = new ExileTargetEffect(exileId, " Foretell Turn Number: " + game.getTurnNum()); + + // remember turn number it was cast + game.getState().setValue(mainCardId.toString() + "Foretell Turn Number", game.getTurnNum()); + + // remember the foretell cost + game.getState().setValue(mainCardId.toString() + "Foretell Cost", foretellCost); + game.getState().setValue(mainCardId.toString() + "Foretell Split Cost", foretellSplitCost); + + // exile the card face-down + effect.setWithName(false); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + effect.apply(game, source); + card.setFaceDown(true, game); + game.addEffect(new ForetellAddCostEffect(new MageObjectReference(card, game)), source); + game.fireEvent(new GameEvent(GameEvent.EventType.CARD_FORETOLD, card.getId(), source, source.getControllerId(), 0, true)); + return true; } + return false; + } +} - protected ForetellAddCostEffect(final ForetellAddCostEffect effect) { - super(effect); - this.mor = effect.mor; - } +class ForetellLookAtCardEffect extends AsThoughEffectImpl { - @Override - public boolean apply(Game game, Ability source) { - Card card = mor.getCard(game); + ForetellLookAtCardEffect() { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.AIDontUseIt); + } + + private ForetellLookAtCardEffect(final ForetellLookAtCardEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public ForetellLookAtCardEffect copy() { + return new ForetellLookAtCardEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + if (affectedControllerId.equals(source.getControllerId())) { + Card card = game.getCard(objectId); if (card != null) { + MageObject sourceObject = game.getObject(source); + if (sourceObject == null) { + return false; + } UUID mainCardId = card.getMainCard().getId(); - if (game.getState().getZone(mainCardId) == Zone.EXILED) { - String foretellCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Cost"); - String foretellSplitCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Split Cost"); - if (card instanceof SplitCard) { - if (foretellCost != null) { - SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard(); + UUID exileId = CardUtil.getExileZoneId(mainCardId.toString() + "foretellAbility", game); + ExileZone exile = game.getExile().getExileZone(exileId); + return exile != null + && exile.contains(mainCardId); + } + } + return false; + } +} + +class ForetellAddCostEffect extends ContinuousEffectImpl { + + private final MageObjectReference mor; + + ForetellAddCostEffect(MageObjectReference mor) { + super(Duration.EndOfGame, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.mor = mor; + staticText = "Foretold card"; + } + + private ForetellAddCostEffect(final ForetellAddCostEffect effect) { + super(effect); + this.mor = effect.mor; + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = mor.getCard(game); + if (card != null) { + UUID mainCardId = card.getMainCard().getId(); + if (game.getState().getZone(mainCardId) == Zone.EXILED) { + String foretellCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Cost"); + String foretellSplitCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Split Cost"); + if (card instanceof SplitCard) { + if (foretellCost != null) { + SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard(); + ForetellCostAbility ability = new ForetellCostAbility(foretellCost); + ability.setSourceId(leftHalfCard.getId()); + ability.setControllerId(source.getControllerId()); + ability.setSpellAbilityType(leftHalfCard.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(leftHalfCard.getName()); + game.getState().addOtherAbility(leftHalfCard, ability); + } + if (foretellSplitCost != null) { + SplitCardHalf rightHalfCard = ((SplitCard) card).getRightHalfCard(); + ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); + ability.setSourceId(rightHalfCard.getId()); + ability.setControllerId(source.getControllerId()); + ability.setSpellAbilityType(rightHalfCard.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(rightHalfCard.getName()); + game.getState().addOtherAbility(rightHalfCard, ability); + } + } else if (card instanceof ModalDoubleFacedCard) { + if (foretellCost != null) { + ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); + // some MDFC's are land IE: sea gate restoration + if (!leftHalfCard.isLand(game)) { ForetellCostAbility ability = new ForetellCostAbility(foretellCost); ability.setSourceId(leftHalfCard.getId()); ability.setControllerId(source.getControllerId()); @@ -223,8 +331,11 @@ public class ForetellAbility extends SpecialAction { ability.setAbilityName(leftHalfCard.getName()); game.getState().addOtherAbility(leftHalfCard, ability); } - if (foretellSplitCost != null) { - SplitCardHalf rightHalfCard = ((SplitCard) card).getRightHalfCard(); + } + if (foretellSplitCost != null) { + ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + // some MDFC's are land IE: sea gate restoration + if (!rightHalfCard.isLand(game)) { ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); ability.setSourceId(rightHalfCard.getId()); ability.setControllerId(source.getControllerId()); @@ -232,240 +343,276 @@ public class ForetellAbility extends SpecialAction { ability.setAbilityName(rightHalfCard.getName()); game.getState().addOtherAbility(rightHalfCard, ability); } - } else if (card instanceof ModalDoubleFacedCard) { - if (foretellCost != null) { - ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); - // some MDFC's are land IE: sea gate restoration - if (!leftHalfCard.isLand(game)) { - ForetellCostAbility ability = new ForetellCostAbility(foretellCost); - ability.setSourceId(leftHalfCard.getId()); - ability.setControllerId(source.getControllerId()); - ability.setSpellAbilityType(leftHalfCard.getSpellAbility().getSpellAbilityType()); - ability.setAbilityName(leftHalfCard.getName()); - game.getState().addOtherAbility(leftHalfCard, ability); - } - } - if (foretellSplitCost != null) { - ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); - // some MDFC's are land IE: sea gate restoration - if (!rightHalfCard.isLand(game)) { - ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); - ability.setSourceId(rightHalfCard.getId()); - ability.setControllerId(source.getControllerId()); - ability.setSpellAbilityType(rightHalfCard.getSpellAbility().getSpellAbilityType()); - ability.setAbilityName(rightHalfCard.getName()); - game.getState().addOtherAbility(rightHalfCard, ability); - } - } - } else if (card instanceof CardWithSpellOption) { - if (foretellCost != null) { - Card creatureCard = card.getMainCard(); - ForetellCostAbility ability = new ForetellCostAbility(foretellCost); - ability.setSourceId(creatureCard.getId()); - ability.setControllerId(source.getControllerId()); - ability.setSpellAbilityType(creatureCard.getSpellAbility().getSpellAbilityType()); - ability.setAbilityName(creatureCard.getName()); - game.getState().addOtherAbility(creatureCard, ability); - } - if (foretellSplitCost != null) { - Card spellCard = ((CardWithSpellOption) card).getSpellCard(); - ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); - ability.setSourceId(spellCard.getId()); - ability.setControllerId(source.getControllerId()); - ability.setSpellAbilityType(spellCard.getSpellAbility().getSpellAbilityType()); - ability.setAbilityName(spellCard.getName()); - game.getState().addOtherAbility(spellCard, ability); - } - } else if (foretellCost != null) { + } + } else if (card instanceof CardWithSpellOption) { + if (foretellCost != null) { + Card creatureCard = card.getMainCard(); ForetellCostAbility ability = new ForetellCostAbility(foretellCost); - ability.setSourceId(card.getId()); + ability.setSourceId(creatureCard.getId()); ability.setControllerId(source.getControllerId()); - ability.setSpellAbilityType(card.getSpellAbility().getSpellAbilityType()); - ability.setAbilityName(card.getName()); - game.getState().addOtherAbility(card, ability); + ability.setSpellAbilityType(creatureCard.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(creatureCard.getName()); + game.getState().addOtherAbility(creatureCard, ability); } - return true; + if (foretellSplitCost != null) { + Card spellCard = ((CardWithSpellOption) card).getSpellCard(); + ForetellCostAbility ability = new ForetellCostAbility(foretellSplitCost); + ability.setSourceId(spellCard.getId()); + ability.setControllerId(source.getControllerId()); + ability.setSpellAbilityType(spellCard.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(spellCard.getName()); + game.getState().addOtherAbility(spellCard, ability); + } + } else if (foretellCost != null) { + ForetellCostAbility ability = new ForetellCostAbility(foretellCost); + ability.setSourceId(card.getId()); + ability.setControllerId(source.getControllerId()); + ability.setSpellAbilityType(card.getSpellAbility().getSpellAbilityType()); + ability.setAbilityName(card.getName()); + game.getState().addOtherAbility(card, ability); } + return true; } - discard(); - return true; - } - - @Override - public ForetellAddCostEffect copy() { - return new ForetellAddCostEffect(this); } + discard(); + return true; } - static class ForetellCostAbility extends SpellAbility { - - private String abilityName; - private SpellAbility spellAbilityToResolve; - - ForetellCostAbility(String foretellCost) { - super(null, "Testing", Zone.EXILED, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.NORMAL); - // Needed for Dream Devourer and Ethereal Valkyrie reducing the cost of a colorless CMC 2 or less spell to 0 - // CardUtil.reduceCost returns an empty string in that case so we add a cost of 0 here - // https://github.com/magefree/mage/issues/7607 - if (foretellCost != null && foretellCost.isEmpty()) { - foretellCost = "{0}"; - } - this.setAdditionalCostsRuleVisible(false); - this.name = "Foretell " + foretellCost; - this.addCost(new ManaCostsImpl<>(foretellCost)); - } - - protected ForetellCostAbility(final ForetellCostAbility ability) { - super(ability); - this.spellAbilityType = ability.spellAbilityType; - this.abilityName = ability.abilityName; - this.spellAbilityToResolve = ability.spellAbilityToResolve; - } - - @Override - public ActivationStatus canActivate(UUID playerId, Game game) { - if (super.canActivate(playerId, game).canActivate()) { - Card card = game.getCard(getSourceId()); - if (card != null) { - UUID mainCardId = card.getMainCard().getId(); - // Card must be in the exile zone - if (game.getState().getZone(mainCardId) != Zone.EXILED) { - return ActivationStatus.getFalse(); - } - Integer foretoldTurn = (Integer) game.getState().getValue(mainCardId.toString() + "Foretell Turn Number"); - UUID exileId = (UUID) game.getState().getValue(mainCardId.toString() + "foretellAbility"); - // Card must be Foretold - if (foretoldTurn == null || exileId == null) { - return ActivationStatus.getFalse(); - } - // Can't be cast if the turn it was Foretold is the same - if (foretoldTurn == game.getTurnNum()) { - return ActivationStatus.getFalse(); - } - // Check that the card is actually in the exile zone (ex: Oblivion Ring exiles it after it was Foretold, etc) - ExileZone exileZone = game.getState().getExile().getExileZone(exileId); - if (exileZone != null - && exileZone.isEmpty()) { - return ActivationStatus.getFalse(); - } - if (card instanceof SplitCard) { - if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { - return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); - } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { - return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); - } - } else if (card instanceof ModalDoubleFacedCard) { - if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { - return ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); - } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { - return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); - } - } else if (card instanceof CardWithSpellOption) { - if (card.getMainCard().getName().equals(abilityName)) { - return card.getMainCard().getSpellAbility().canActivate(playerId, game); - } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) { - return ((CardWithSpellOption) card).getSpellCard().getSpellAbility().canActivate(playerId, game); - } - } - return card.getSpellAbility().canActivate(playerId, game); - } - } - return ActivationStatus.getFalse(); - } - - @Override - public SpellAbility getSpellAbilityToResolve(Game game) { - Card card = game.getCard(getSourceId()); - if (card != null) { - if (spellAbilityToResolve == null) { - SpellAbility spellAbilityCopy = null; - if (card instanceof SplitCard) { - if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); - } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); - } - } else if (card instanceof ModalDoubleFacedCard) { - if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().copy(); - } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { - spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy(); - } - } else if (card instanceof CardWithSpellOption) { - if (card.getMainCard().getName().equals(abilityName)) { - spellAbilityCopy = card.getMainCard().getSpellAbility().copy(); - } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) { - spellAbilityCopy = ((CardWithSpellOption) card).getSpellCard().getSpellAbility().copy(); - } - } else { - spellAbilityCopy = card.getSpellAbility().copy(); - } - if (spellAbilityCopy == null) { - return null; - } - spellAbilityCopy.setId(this.getId()); - spellAbilityCopy.clearManaCosts(); - spellAbilityCopy.clearManaCostsToPay(); - spellAbilityCopy.addCost(this.getCosts().copy()); - spellAbilityCopy.addCost(this.getManaCosts().copy()); - spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); - spellAbilityToResolve = spellAbilityCopy; - } - } - return spellAbilityToResolve; - } - - @Override - public Costs getCosts() { - if (spellAbilityToResolve == null) { - return super.getCosts(); - } - return spellAbilityToResolve.getCosts(); - } - - @Override - public ForetellCostAbility copy() { - return new ForetellCostAbility(this); - } - - @Override - public String getRule(boolean all) { - StringBuilder sbRule = new StringBuilder("Foretell"); - if (!getCosts().isEmpty()) { - sbRule.append("—"); - } else { - sbRule.append(' '); - } - if (!getManaCosts().isEmpty()) { - sbRule.append(getManaCosts().getText()); - } - if (!getCosts().isEmpty()) { - if (!getManaCosts().isEmpty()) { - sbRule.append(", "); - } - sbRule.append(getCosts().getText()); - sbRule.append('.'); - } - if (abilityName != null) { - sbRule.append(' '); - sbRule.append(abilityName); - } - sbRule.append(" (You may cast this card from exile for its foretell cost.)"); - return sbRule.toString(); - } - - /** - * Used for split card in PlayerImpl method: - * getOtherUseableActivatedAbilities - */ - public void setAbilityName(String abilityName) { - this.abilityName = abilityName; - } - - } - - public static boolean isCardInForetell(Card card, Game game) { - // searching ForetellCostAbility - it adds for foretelled cards only after exile - return card.getAbilities(game).containsClass(ForetellCostAbility.class); + @Override + public ForetellAddCostEffect copy() { + return new ForetellAddCostEffect(this); + } +} + +class ForetellCostAbility extends SpellAbility { + + private String abilityName; + private SpellAbility spellAbilityToResolve; + + ForetellCostAbility(String foretellCost) { + super(null, "Testing", Zone.EXILED, SpellAbilityType.BASE_ALTERNATE, SpellAbilityCastMode.NORMAL); + // Needed for Dream Devourer and Ethereal Valkyrie reducing the cost of a colorless CMC 2 or less spell to 0 + // CardUtil.reduceCost returns an empty string in that case so we add a cost of 0 here + // https://github.com/magefree/mage/issues/7607 + if (foretellCost != null && foretellCost.isEmpty()) { + foretellCost = "{0}"; + } + this.setAdditionalCostsRuleVisible(false); + this.name = "Foretell " + foretellCost; + this.addCost(new ManaCostsImpl<>(foretellCost)); + } + + private ForetellCostAbility(final ForetellCostAbility ability) { + super(ability); + this.spellAbilityType = ability.spellAbilityType; + this.abilityName = ability.abilityName; + this.spellAbilityToResolve = ability.spellAbilityToResolve; + } + + @Override + public ActivationStatus canActivate(UUID playerId, Game game) { + if (super.canActivate(playerId, game).canActivate()) { + Card card = game.getCard(getSourceId()); + if (card != null) { + UUID mainCardId = card.getMainCard().getId(); + // Card must be in the exile zone + if (game.getState().getZone(mainCardId) != Zone.EXILED) { + return ActivationStatus.getFalse(); + } + Integer foretoldTurn = (Integer) game.getState().getValue(mainCardId.toString() + "Foretell Turn Number"); + UUID exileId = (UUID) game.getState().getValue(mainCardId.toString() + "foretellAbility"); + // Card must be Foretold + if (foretoldTurn == null || exileId == null) { + return ActivationStatus.getFalse(); + } + // Can't be cast if the turn it was Foretold is the same + if (foretoldTurn == game.getTurnNum()) { + return ActivationStatus.getFalse(); + } + // Check that the card is actually in the exile zone (ex: Oblivion Ring exiles it after it was Foretold, etc) + ExileZone exileZone = game.getState().getExile().getExileZone(exileId); + if (exileZone != null + && exileZone.isEmpty()) { + return ActivationStatus.getFalse(); + } + if (card instanceof SplitCard) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } else if (card instanceof ModalDoubleFacedCard) { + if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { + return ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game); + } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { + return ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game); + } + } else if (card instanceof CardWithSpellOption) { + if (card.getMainCard().getName().equals(abilityName)) { + return card.getMainCard().getSpellAbility().canActivate(playerId, game); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) { + return ((CardWithSpellOption) card).getSpellCard().getSpellAbility().canActivate(playerId, game); + } + } + return card.getSpellAbility().canActivate(playerId, game); + } + } + return ActivationStatus.getFalse(); + } + + @Override + public SpellAbility getSpellAbilityToResolve(Game game) { + Card card = game.getCard(getSourceId()); + if (card != null) { + if (spellAbilityToResolve == null) { + SpellAbility spellAbilityCopy = null; + if (card instanceof SplitCard) { + if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy(); + } + } else if (card instanceof ModalDoubleFacedCard) { + if (((ModalDoubleFacedCard) card).getLeftHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((ModalDoubleFacedCard) card).getLeftHalfCard().getSpellAbility().copy(); + } else if (((ModalDoubleFacedCard) card).getRightHalfCard().getName().equals(abilityName)) { + spellAbilityCopy = ((ModalDoubleFacedCard) card).getRightHalfCard().getSpellAbility().copy(); + } + } else if (card instanceof CardWithSpellOption) { + if (card.getMainCard().getName().equals(abilityName)) { + spellAbilityCopy = card.getMainCard().getSpellAbility().copy(); + } else if (((CardWithSpellOption) card).getSpellCard().getName().equals(abilityName)) { + spellAbilityCopy = ((CardWithSpellOption) card).getSpellCard().getSpellAbility().copy(); + } + } else { + spellAbilityCopy = card.getSpellAbility().copy(); + } + if (spellAbilityCopy == null) { + return null; + } + spellAbilityCopy.setId(this.getId()); + spellAbilityCopy.clearManaCosts(); + spellAbilityCopy.clearManaCostsToPay(); + spellAbilityCopy.addCost(this.getCosts().copy()); + spellAbilityCopy.addCost(this.getManaCosts().copy()); + spellAbilityCopy.setSpellAbilityCastMode(this.getSpellAbilityCastMode()); + spellAbilityToResolve = spellAbilityCopy; + } + } + return spellAbilityToResolve; + } + + @Override + public Costs getCosts() { + if (spellAbilityToResolve == null) { + return super.getCosts(); + } + return spellAbilityToResolve.getCosts(); + } + + @Override + public ForetellCostAbility copy() { + return new ForetellCostAbility(this); + } + + @Override + public String getRule(boolean all) { + StringBuilder sbRule = new StringBuilder("Foretell"); + if (!getCosts().isEmpty()) { + sbRule.append("—"); + } else { + sbRule.append(' '); + } + if (!getManaCosts().isEmpty()) { + sbRule.append(getManaCosts().getText()); + } + if (!getCosts().isEmpty()) { + if (!getManaCosts().isEmpty()) { + sbRule.append(", "); + } + sbRule.append(getCosts().getText()); + sbRule.append('.'); + } + if (abilityName != null) { + sbRule.append(' '); + sbRule.append(abilityName); + } + sbRule.append(" (You may cast this card from exile for its foretell cost.)"); + return sbRule.toString(); + } + + /** + * Used for split card in PlayerImpl method: + * getOtherUseableActivatedAbilities + */ + void setAbilityName(String abilityName) { + this.abilityName = abilityName; + } + +} + +class ForetellAddAbilityEffect extends ContinuousEffectImpl { + + private static final FilterNonlandCard filter = new FilterNonlandCard(); + + static { + filter.add(Predicates.not(new AbilityPredicate(ForetellAbility.class))); + } + + ForetellAddAbilityEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.staticText = "Each nonland card in your hand without foretell has foretell. Its foretell cost is equal to its mana cost reduced by {2}"; + } + + private ForetellAddAbilityEffect(final ForetellAddAbilityEffect effect) { + super(effect); + } + + @Override + public ForetellAddAbilityEffect copy() { + return new ForetellAddAbilityEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + for (Card card : controller.getHand().getCards(filter, game)) { + ForetellAbility foretellAbility = null; + if (card instanceof SplitCard) { + String leftHalfCost = CardUtil.reduceCost(((SplitCard) card).getLeftHalfCard().getManaCost(), 2).getText(); + String rightHalfCost = CardUtil.reduceCost(((SplitCard) card).getRightHalfCard().getManaCost(), 2).getText(); + foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); + } else if (card instanceof ModalDoubleFacedCard) { + ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard(); + // If front side of MDFC is land, do nothing as Dream Devourer does not apply to lands + // MDFC cards in hand are considered lands if front side is land + if (!leftHalfCard.isLand(game)) { + String leftHalfCost = CardUtil.reduceCost(leftHalfCard.getManaCost(), 2).getText(); + ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard(); + if (rightHalfCard.isLand(game)) { + foretellAbility = new ForetellAbility(card, leftHalfCost); + } else { + String rightHalfCost = CardUtil.reduceCost(rightHalfCard.getManaCost(), 2).getText(); + foretellAbility = new ForetellAbility(card, leftHalfCost, rightHalfCost); + } + } + } else if (card instanceof CardWithSpellOption) { + String creatureCost = CardUtil.reduceCost(card.getMainCard().getManaCost(), 2).getText(); + String spellCost = CardUtil.reduceCost(((CardWithSpellOption) card).getSpellCard().getManaCost(), 2).getText(); + foretellAbility = new ForetellAbility(card, creatureCost, spellCost); + } else { + String costText = CardUtil.reduceCost(card.getManaCost(), 2).getText(); + foretellAbility = new ForetellAbility(card, costText); + } + if (foretellAbility != null) { + foretellAbility.setSourceId(card.getId()); + foretellAbility.setControllerId(card.getOwnerId()); + game.getState().addOtherAbility(card, foretellAbility); + } + } + return true; } } diff --git a/Mage/src/main/java/mage/abilities/keyword/MayhemAbility.java b/Mage/src/main/java/mage/abilities/keyword/MayhemAbility.java index 4b0478a40ba..5563f9940dc 100644 --- a/Mage/src/main/java/mage/abilities/keyword/MayhemAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/MayhemAbility.java @@ -1,16 +1,19 @@ package mage.abilities.keyword; -import mage.ApprovingObject; import mage.MageIdentifier; +import mage.MageObjectReference; import mage.abilities.SpellAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.cards.Card; import mage.constants.SpellAbilityType; +import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; -import mage.players.Player; -import mage.watchers.common.CastSpellLastTurnWatcher; +import mage.game.events.DiscardedCardsEvent; +import mage.game.events.GameEvent; +import mage.watchers.Watcher; +import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -33,10 +36,10 @@ public class MayhemAbility extends SpellAbility { this.clearManaCosts(); this.clearManaCostsToPay(); this.addCost(new ManaCostsImpl<>(manaString)); - + this.addWatcher(new MayhemWatcher()); this.setRuleAtTheTop(true); - this.rule = "Surge " + manaString + - " (You may cast this card from your graveyard for "+manaString+ + this.rule = "Mayhem " + manaString + + " (You may cast this card from your graveyard for " + manaString + " if you discarded it this turn. Timing rules still apply.)"; } @@ -47,8 +50,11 @@ public class MayhemAbility extends SpellAbility { @Override public ActivationStatus canActivate(UUID playerId, Game game) { - // TODO: Implement this - return super.canActivate(playerId,game); + if (!Zone.GRAVEYARD.match(game.getState().getZone(getSourceId())) + || !MayhemWatcher.checkCard(getSourceId(), game)) { + return ActivationStatus.getFalse(); + } + return super.canActivate(playerId, game); } @Override @@ -69,5 +75,38 @@ public class MayhemAbility extends SpellAbility { public String getRule() { return rule; } - +} + +class MayhemWatcher extends Watcher { + + private final Set set = new HashSet<>(); + + MayhemWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() != GameEvent.EventType.DISCARDED_CARDS) { + return; + } + for (Card card : ((DiscardedCardsEvent) event).getDiscardedCards().getCards(game)) { + set.add(new MageObjectReference(card, game)); + } + } + + @Override + public void reset() { + super.reset(); + set.clear(); + } + + static boolean checkCard(UUID cardId, Game game) { + return game + .getState() + .getWatcher(MayhemWatcher.class) + .set + .stream() + .anyMatch(mor -> mor.refersTo(cardId, game)); + } } diff --git a/Mage/src/main/java/mage/filter/FilterCard.java b/Mage/src/main/java/mage/filter/FilterCard.java index c4eb8cf57ef..553fac22335 100644 --- a/Mage/src/main/java/mage/filter/FilterCard.java +++ b/Mage/src/main/java/mage/filter/FilterCard.java @@ -2,6 +2,7 @@ package mage.filter; import mage.abilities.Ability; import mage.cards.Card; +import mage.constants.SubType; import mage.constants.TargetController; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; @@ -29,7 +30,18 @@ public class FilterCard extends FilterObject { } public FilterCard(String name) { + this(null, name); + } + + public FilterCard(SubType subType) { + this(subType, subType + " card"); + } + + public FilterCard(SubType subType, String name) { super(name); + if (subType != null) { + this.add(subType.getPredicate()); + } } protected FilterCard(final FilterCard filter) { diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java index 05e18a8be30..22e9caa2ea1 100644 --- a/Mage/src/main/java/mage/filter/StaticFilters.java +++ b/Mage/src/main/java/mage/filter/StaticFilters.java @@ -185,24 +185,33 @@ public final class StaticFilters { FILTER_CARD_LAND_A.setLockedFilter(true); } - public static final FilterBasicLandCard FILTER_CARD_BASIC_LAND = new FilterBasicLandCard(); + public static final FilterLandCard FILTER_CARD_BASIC_LAND = new FilterLandCard("basic land card"); static { + FILTER_CARD_BASIC_LAND.add(SuperType.BASIC.getPredicate()); FILTER_CARD_BASIC_LAND.setLockedFilter(true); } - public static final FilterBasicLandCard FILTER_CARD_BASIC_LANDS = new FilterBasicLandCard("basic land cards"); + public static final FilterLandCard FILTER_CARD_BASIC_LANDS = new FilterLandCard("basic land cards"); static { + FILTER_CARD_BASIC_LANDS.add(SuperType.BASIC.getPredicate()); FILTER_CARD_BASIC_LANDS.setLockedFilter(true); } - public static final FilterBasicLandCard FILTER_CARD_BASIC_LAND_A = new FilterBasicLandCard("a basic land card"); + public static final FilterLandCard FILTER_CARD_BASIC_LAND_A = new FilterLandCard("a basic land card"); static { + FILTER_CARD_BASIC_LAND_A.add(SuperType.BASIC.getPredicate()); FILTER_CARD_BASIC_LAND_A.setLockedFilter(true); } + public static final FilterBasicCard FILTER_CARD_BASIC_PLAINS = new FilterBasicCard(SubType.PLAINS); + + static { + FILTER_CARD_BASIC_PLAINS.setLockedFilter(true); + } + public static final FilterNonlandCard FILTER_CARD_NON_LAND = new FilterNonlandCard(); static { diff --git a/Mage/src/main/java/mage/filter/common/FilterBasicCard.java b/Mage/src/main/java/mage/filter/common/FilterBasicCard.java new file mode 100644 index 00000000000..5996f7d9042 --- /dev/null +++ b/Mage/src/main/java/mage/filter/common/FilterBasicCard.java @@ -0,0 +1,33 @@ +package mage.filter.common; + +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterCard; + +/** + * @author BetaSteward_at_googlemail.com + */ +public class FilterBasicCard extends FilterCard { + + public FilterBasicCard(SubType subType) { + this(subType, "basic " + subType + " card"); + } + + public FilterBasicCard(String name) { + this(null, name); + } + + public FilterBasicCard(SubType subType, String name) { + super(subType, name); + this.add(SuperType.BASIC.getPredicate()); + } + + protected FilterBasicCard(final FilterBasicCard filter) { + super(filter); + } + + @Override + public FilterBasicCard copy() { + return new FilterBasicCard(this); + } +} diff --git a/Mage/src/main/java/mage/filter/common/FilterBasicLandCard.java b/Mage/src/main/java/mage/filter/common/FilterBasicLandCard.java deleted file mode 100644 index 521d283b722..00000000000 --- a/Mage/src/main/java/mage/filter/common/FilterBasicLandCard.java +++ /dev/null @@ -1,37 +0,0 @@ - -package mage.filter.common; - -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.SuperType; -import mage.filter.FilterCard; - -/** - * @author BetaSteward_at_googlemail.com - */ -public class FilterBasicLandCard extends FilterCard { - - public FilterBasicLandCard() { - this("basic land card"); - } - - public FilterBasicLandCard(SubType subType) { - this("basic " + subType + " card"); - this.add(subType.getPredicate()); - } - - public FilterBasicLandCard(String name) { - super(name); - this.add(CardType.LAND.getPredicate()); - this.add(SuperType.BASIC.getPredicate()); - } - - protected FilterBasicLandCard(final FilterBasicLandCard filter) { - super(filter); - } - - @Override - public FilterBasicLandCard copy() { - return new FilterBasicLandCard(this); - } -} diff --git a/Mage/src/main/java/mage/filter/common/FilterBySubtypeCard.java b/Mage/src/main/java/mage/filter/common/FilterBySubtypeCard.java deleted file mode 100644 index b5bb2f023e4..00000000000 --- a/Mage/src/main/java/mage/filter/common/FilterBySubtypeCard.java +++ /dev/null @@ -1,31 +0,0 @@ -package mage.filter.common; - -import mage.constants.SubType; -import mage.filter.FilterCard; - -/** - * TODO: Collapse this into FilterCard - * - * @author LevelX2 - */ - -public class FilterBySubtypeCard extends FilterCard { - - public FilterBySubtypeCard(SubType subtype) { - this(subtype, subtype + " card"); - } - - public FilterBySubtypeCard(SubType subtype, String name) { - super(name); - this.add(subtype.getPredicate()); - } - - protected FilterBySubtypeCard(final FilterBySubtypeCard filter) { - super(filter); - } - - @Override - public FilterBySubtypeCard copy() { - return new FilterBySubtypeCard(this); - } -} diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 632c2ede065..21ad27b5d95 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -615,8 +615,12 @@ public class GameEvent implements Serializable { DUNGEON_COMPLETED, TEMPTED_BY_RING, RING_BEARER_CHOSEN, REMOVED_FROM_COMBAT, // targetId id of permanent removed from combat - FORETOLD, // targetId id of card foretold - FORETELL, // targetId id of card foretell playerId id of the controller + /* card foretold + targetId id of card foretold + playerId id of player foretelling card + flag true if player did foretell, false if became foretold without foretell + */ + CARD_FORETOLD, /* villainous choice targetId player making the choice sourceId sourceId of the ability forcing the choice diff --git a/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java b/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java index 3213e8b76de..b37b0edf9f3 100644 --- a/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/ForetoldWatcher.java @@ -1,12 +1,13 @@ package mage.watchers.common; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; +import java.util.*; + +import mage.MageObjectReference; import mage.cards.Card; import mage.constants.WatcherScope; import mage.game.Game; import mage.game.events.GameEvent; +import mage.players.Player; import mage.util.CardUtil; import mage.watchers.Watcher; @@ -16,9 +17,12 @@ import mage.watchers.Watcher; */ public class ForetoldWatcher extends Watcher { - // If foretell was activated or a card was Foretold by the controller this turn, this list stores it. Cleared at the end of the turn. - private final Set foretellCardsThisTurn = new HashSet<>(); - private final Set foretoldCards = new HashSet<>(); + private final Set foretoldCards = new HashSet<>(); + // cards foretold - ZCC stored to reference from stack (exile zone plus 1) + + private final Map playerForetellCount = new HashMap<>(); + // map of player id to number of times they foretell a card, cleared each turn + public ForetoldWatcher() { super(WatcherScope.GAME); @@ -26,35 +30,32 @@ public class ForetoldWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.FORETELL) { - Card card = game.getCard(event.getTargetId()); - if (card != null - && controllerId == event.getPlayerId()) { - foretellCardsThisTurn.add(card.getId()); - foretoldCards.add(card.getId()); - } + if (event.getType() != GameEvent.EventType.CARD_FORETOLD) { + return; } - // Ethereal Valkyrie - if (event.getType() == GameEvent.EventType.FORETOLD) { - Card card = game.getCard(event.getTargetId()); - if (card != null) { - // Ethereal Valkyrie does not Foretell the card, it becomes Foretold, so don't add it to the Foretell list - foretoldCards.add(card.getId()); + Card card = game.getCard(event.getTargetId()); + if (card != null) { + foretoldCards.add(new MageObjectReference(card, game, 1)); + } + if (event.getFlag()) { + Player player = game.getPlayer(event.getPlayerId()); + if (player != null) { + playerForetellCount.compute(player.getId(), CardUtil::setOrIncrementValue); } } } - public boolean cardWasForetold(UUID sourceId) { - return foretoldCards.contains(sourceId); + public boolean checkForetold(UUID sourceId, Game game) { + return foretoldCards.contains(new MageObjectReference(sourceId, game)); } - public int countNumberForetellThisTurn() { - return foretellCardsThisTurn.size(); + public int getPlayerForetellCountThisTurn(UUID playerId) { + return playerForetellCount.getOrDefault(playerId, 0); } @Override public void reset() { super.reset(); - foretellCardsThisTurn.clear(); + playerForetellCount.clear(); } } diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 473eac70a0e..f4216fda947 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -59720,12 +59720,14 @@ Viridescent Bog|Edge of Eternities Commander|190|R||Land|||{1}, {T}: Add {B}{G}. Wastes|Edge of Eternities Commander|191|C||Basic Land|||{T}: Add {C}.| Avatar Aang|Avatar: The Last Airbender|363|M|{R}{G}{W}{U}|Legendary Creature - Human Avatar Ally|4|4|Flying, firebending 2$Whenever you waterbend, earthbend, firebend, or airbend, draw a card. Then if you've done all four this turn, transform Avatar Aang.| Aang, Master of Elements|Avatar: The Last Airbender|363|M||Legendary Creature - Avatar Ally|6|6|Flying$Spelsl you cast cost {W}{U}{B}{R}{G} less to cast.$At the beginning of each upkeep, you may transform Aang, Master of Elements. If you do, you gain 4 life, draw four cards, put four +1/+1 counters on him, and he deals 4 damage to each opponent.| +Anti-Venom, Horrifying Healer|Marvel's Spider-Man|1|M|{W}{W}{W}{W}{W}|Legendary Creature - Symbiote Hero|5|5|When Anti-Venom enters, if he was cast, return target creature card from your graveyard to the battlefield. If damage would be dealt to Anti-Venom, prevent that damage and put that many +1/+1 counters on him.| Aunt May|Marvel's Spider-Man|3|U|{W}|Legendary Creature - Human Citizen|0|2|Whenever another creature you control enters, you gain 1 life. If it's a Spider, put a +1/+1 counter on it.| Daily Bugle Reporters|Marvel's Spider-Man|6|C|{3}{W}|Creature - Human Citizen|2|3|When this creature enters, choose one --$* Puff Piece -- Put a +1/+1 counter on each of up to two target creatures.$* Investigative Journalism -- Return target creature card with mana value 2 or less from your graveyard to your hand.| Origin of Spider-Man|Marvel's Spider-Man|9|R|{1}{W}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Create a 2/1 green Spider creature token with reach.$II -- Put a +1/+1 counter on target creature you control. It becomes a legendary Spider Hero in addition to its other types.$III -- Target creature you control gains double strike until end of turn.| Peter Parker|Marvel's Spider-Man|10|M|{1}{W}|Legendary Creature - Human Scientist Hero|0|1|When Peter Parker enters, create a 2/1 green Spider creature token with reach.${1}{G}{W}{U}: Transform Peter Parker. Activate only as a sorcery.| Amazing Spider-Man|Marvel's Spider-Man|10|M|{1}{G}{W}{U}|Legendary Creature - Spider Human Hero|4|4|Vigilance, reach$Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}.| Selfless Police Captain|Marvel's Spider-Man|12|C|{1}{W}|Creature - Human Detective|1|1|This creature enters with a +1/+1 counter on it.$When this creature leaves the battlefield, put its +1/+1 counters on target creature you control.| +Spectacular Spider-Man|Marvel's Spider-Man|14|R|{1}{W}|Legendary Creature - Spider Human Hero|3|2|Flash 1: Spectacular Spider-Man gains flying until end of turn. 1, Sacrifice Spectacular Spider-Man: Creatures you control gain hexproof and indestructible until end of turn.| Spectacular Tactics|Marvel's Spider-Man|15|C|{1}{W}|Instant|||Choose one --$* Put a +1/+1 counter on target creature you control. It gains hexproof until end of turn.$* Destroy target creature with power 4 or greater.| Spider-Man, Web-Slinger|Marvel's Spider-Man|16|C|{2}{W}|Legendary Creature - Spider Human Hero|3|3|Web-slinging {W}| Starling, Aerial Ally|Marvel's Spider-Man|18|C|{4}{W}|Legendary Creature - Human Hero|3|4|Flying$When Starling enters, another target creature you control gains flying until end of turn.| @@ -59750,12 +59752,15 @@ Venom, Evil Unleashed|Marvel's Spider-Man|71|C|{4}{B}|Legendary Creature - Symbi Venom's Hunger|Marvel's Spider-Man|73|C|{4}{B}|Sorcery|||This spell costs {2} less to cast if you control a Villain.$Destroy target creature. You gain 2 life.| Angry Rabble|Marvel's Spider-Man|75|C|{1}{R}|Creature - Human Citizen|2|2|Trample$Whenever you cast a spell with mana value 4 or greater, this creature deals 1 damage to each opponent.${5}{R}: Put two +1/+1 counters on this creature. Activate only as a sorcery.| Electro's Bolt|Marvel's Spider-Man|77|C|{2}{R}|Sorcery|||Electro's Bolt deals 4 damage to target creature.$Mayhem {1}{R}| +Gwen Stacy|Marvel's Spider-Man|78|M|{1}{R}|Legendary Creature - Human Performer Hero|2|1|When Gwen Stacy enters, exile the top card of your library. You may play that card for as long as you control this creature.${2}{U}{R}{W}: Transform Gwen Stacy. Activate only as a sorcery.| +Ghost-Spider|Marvel's Spider-Man|78|M|{2}{U}{R}{W}|Legendary Creature - Spider Human Hero|2|1 Creature|Flying, vigilance, haste$Whenever you play a land from exile or cast a spell from exile, put a +1/+1 counter on Ghost-Spider.$Remove two counters from Ghost- Spider: Exile the top card of your library. You may play that card this turn.| Masked Meower|Marvel's Spider-Man|82|C|{R}|Creature - Spider Cat Hero|1|1|Haste$Discard a card, Sacrifice this creature: Draw a card.| Romantic Rendezvous|Marvel's Spider-Man|86|C|{1}{R}|Sorcery|||Discard a card, then draw two cards.| Shock|Marvel's Spider-Man|88|C|{R}|Instant|||Shock deals 2 damage to any target.| Shocker, Unshakable|Marvel's Spider-Man|89|U|{4}{R}{R}|Legendary Creature - Human Rogue Villain|5|5|During your turn, Shocker has first strike.$Vibro-Shock Gauntlets -- When Shocker enters, he deals 2 damage to target creature and 2 damage to that creature's controller.| Spider-Gwen, Free Spirit|Marvel's Spider-Man|90|C|{2}{R}|Legendary Creature - Spider Human Hero|2|3|Reach$Whenever Spider-Gwen becomes tapped, you may discard a card. If you do, draw a card.| Spider-Islanders|Marvel's Spider-Man|91|C|{3}{R}|Creature - Spider Horror Citizen|4|3|Mayhem {1}{R}| +Spider-Punk|Marvel's Spider-Man|92|R|{1}{R}|Legendary Creature - Spider Human Hero|2|1|Riot$Other Spiders you control have riot.$Spells and abilities can't be countered.$Damage can't be prevented.| Stegron the Dinosaur Man|Marvel's Spider-Man|95|C|{4}{R}|Legendary Creature - Dinosaur Villain|5|4|Menace$Dinosaur Formula -- {1}{R}, Discard this card: Until end of turn, target creature you control gets +3/+1 and becomes a Dinosaur in addition to its other types.| Taxi Driver|Marvel's Spider-Man|97|C|{1}{R}|Creature - Human Pilot|3|1|{1}, {T}: Target creature gains haste until end of turn.| Grow Extra Arms|Marvel's Spider-Man|101|C|{1}{G}|Instant|||This spell costs {1} less to cast if it targets a Spider.$Target creature gets +4/+4 until end of turn.| @@ -59763,19 +59768,57 @@ Guy in the Chair|Marvel's Spider-Man|102|C|{2}{G}|Creature - Human Advisor|2|3|{ Kapow!|Marvel's Spider-Man|103|C|{2}{G}|Sorcery|||Put a +1/+1 counter on target creature you control. It fights target creature an opponent controls.| Kraven's Cats|Marvel's Spider-Man|104|C|{1}{G}|Creature - Cat Villain|2|2|{2}{G}: This creature gets +2/+2 until end of turn. Activate only once each turn.| Lurking Lizards|Marvel's Spider-Man|107|C|{1}{G}|Creature - Lizard Villain|1|3|Trample$Whenever you cast a spell with mana value 4 or greater, put a +1/+1 counter on this creature.| +Miles Morales|Marvel's Spider-Man|108|M|{1}{G}|Legendary Creature - Human Citizen Hero|1|2|When Miles Morales enters, put a +1/+1 counter on each of up to two target creatures.${3}{R}{G}{W}: Transform Miles Morales. Activate only as a sorcery.| +Ultimate Spider-Man|Marvel's Spider-Man|108|M|{3}{R}{G}{W}|Legendary Creature - Spider Human Hero|4|3|First strike, haste$Camouflage -- {2}: Put a +1/+1 counter on Ultimate Spider-Man. He gains hexproof and becomes colorless until end of turn.$Whenever you attack, double the number of each kind of counter on each Spider and legendary creature you control.| Scout the City|Marvel's Spider-Man|113|C|{1}{G}|Sorcery|||Choose one --$* Look Around -- Mill three cards. You may put a permanent card from among them into your hand. You gain 3 life.$* Bring Down -- Destroy target creature with flying.| Spider-Ham, Peter Porker|Marvel's Spider-Man|114|R|{1}{G}|Legendary Creature - Spider Boar Hero|2|2|When Spider-Ham enters, create a Food token.$Animal May-Ham -- Other Spiders, Boars, Bats, Bears, Birds, Cats, Dogs, Frogs, Jackals, Lizards, Mice, Otters, Rabbits, Raccoons, Rats, Squirrels, Turtles, and Wolves you control get +1/+1.| Spider-Man, Brooklyn Visionary|Marvel's Spider-Man|115|C|{4}{G}|Legendary Creature - Spider Human Hero|4|3|Web-slinging {2}{G}$When Spider-Man enters, search your library for a basic land card, put it onto the battlefield tapped, then shuffle.| Spider-Rex, Daring Dino|Marvel's Spider-Man|116|C|{4}{G}{G}|Legendary Creature - Spider Dinosaur Hero|6|6|Reach, trample$Ward {2}| +Doctor Octopus, Master Planner|Marvel's Spider-Man|128|M|{5}{U}{B}|Legendary Creature - Human Scientist Villain|4|8|Other Villains you control get +2/+2.$Your maximum hand size is eight.$At the beginning of your end step, if you have fewer than eight cards in hand, draw cards equal to the difference.| +Green Goblin, Revenant|Marvel's Spider-Man|130|U|{3}{B}{R}|Legendary Creature - Goblin Human Villain|3|3|Flying, deathtouch$Whenever Green Goblin attacks, discard a card. Then draw a card for each card you've discarded this turn.| SP//dr, Piloted by Peni|Marvel's Spider-Man|147|U|{3}{W}{U}|Legendary Artifact Creature - Spider Hero|4|4|Vigilance$When SP//dr enters, put a +1/+1 counter on target creature.$Whenever a modified creature you control deals combat damage to a player, draw a card.| Spider-Man 2099|Marvel's Spider-Man|150|R|{U}{R}|Legendary Creature - Spider Human Hero|2|3|From the Future -- You can't cast Spider-Man 2099 during your first, second, or third turns of the game.$Double strike, vigilance$At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, Spider-Man 2099 deals damage equal to his power to any target.| +Symbiote Spider-Man|Marvel's Spider-Man|156|R|{2}{U/B}|Legendary Creature - Symbiote Spider Hero|2|4|Whenever this creature deals combat damage to a player, look at that many cards from the top of your library. Put one of them into your hand and the rest into your graveyard.$Find New Host -- {2}{U/B}, Exile this card from your graveyard: Put a +1/+1 counter on target creature you control. It gains this card's other abilities. Activate only as a sorcery.| +Web-Warriors|Marvel's Spider-Man|159|U|{4}{G/W}|Creature - Spider Hero|4|3|When this creature enters, put a +1/+1 counter on each other creature you control.| Eerie Gravestone|Marvel's Spider-Man|163|C|{2}|Artifact|||When this artifact enters, draw a card.${1}{B}, Sacrifice this artifact: Mill four cards. You may put a creature card from among them into your hand.| +Iron Spider, Stark Upgrade|Marvel's Spider-Man|166|R|{3}|Legendary Artifact Creature - Spider Hero|2|3|Vigilance${T}: Put a +1/+1 counter on each artifact creature and/or Vehicle you control.${2}, Remove two +1/+1 counters from among artifacts you control: Draw a card.| Mechanical Mobster|Marvel's Spider-Man|168|C|{3}|Artifact Creature - Human Robot Villain|2|1|When this creature enters, exile up to one target card from a graveyard. Target creature you control connives.| Spider-Bot|Marvel's Spider-Man|173|C|{2}|Artifact Creature - Spider Robot Scout|2|1|Reach$When this creature enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top.| +Multiversal Passage|Marvel's Spider-Man|180|R||Land|||As this land enters, choose a basic land type. Then you may pay 2 life. If you don't, it enters tapped.$This land is the chosen type.| +Plains|Marvel's Spider-Man|189|C||Basic Land - Plains|||({T}: Add {W}.)| +Island|Marvel's Spider-Man|190|C||Basic Land - Island|||({T}: Add {U}.)| +Swamp|Marvel's Spider-Man|191|C||Basic Land - Swamp|||({T}: Add {B}.)| +Mountain|Marvel's Spider-Man|192|C||Basic Land - Mountain|||({T}: Add {R}.)| +Forest|Marvel's Spider-Man|193|C||Basic Land - Forest|||({T}: Add {G}.)| Plains|Marvel's Spider-Man|194|C||Basic Land - Plains|||({T}: Add {W}.)| Island|Marvel's Spider-Man|195|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|Marvel's Spider-Man|196|C||Basic Land - Swamp|||({T}: Add {B}.)| Mountain|Marvel's Spider-Man|197|C||Basic Land - Mountain|||({T}: Add {R}.)| Forest|Marvel's Spider-Man|198|C||Basic Land - Forest|||({T}: Add {G}.)| +Miles Morales|Marvel's Spider-Man|200|M|{1}{G}|Legendary Creature - Human Citizen Hero|1|2|When Miles Morales enters, put a +1/+1 counter on each of up to two target creatures.${3}{R}{G}{W}: Transform Miles Morales. Activate only as a sorcery.| +Ultimate Spider-Man|Marvel's Spider-Man|200|M|{3}{R}{G}{W}|Legendary Creature - Spider Human Hero|4|3|First strike, haste$Camouflage -- {2}: Put a +1/+1 counter on Ultimate Spider-Man. He gains hexproof and becomes colorless until end of turn.$Whenever you attack, double the number of each kind of counter on each Spider and legendary creature you control.| +Spider-Ham, Peter Porker|Marvel's Spider-Man|201|R|{1}{G}|Legendary Creature - Spider Boar Hero|2|2|When Spider-Ham enters, create a Food token.$Animal May-Ham -- Other Spiders, Boars, Bats, Bears, Birds, Cats, Dogs, Frogs, Jackals, Lizards, Mice, Otters, Rabbits, Raccoons, Rats, Squirrels, Turtles, and Wolves you control get +1/+1.| +Gwen Stacy|Marvel's Spider-Man|202|M|{1}{R}|Legendary Creature - Human Performer Hero|2|1|When Gwen Stacy enters, exile the top card of your library. You may play that card for as long as you control this creature.${2}{U}{R}{W}: Transform Gwen Stacy. Activate only as a sorcery.| +Ghost-Spider|Marvel's Spider-Man|202|M|{2}{U}{R}{W}|Legendary Creature - Spider Human Hero|2|1 Creature|Flying, vigilance, haste$Whenever you play a land from exile or cast a spell from exile, put a +1/+1 counter on Ghost-Spider.$Remove two counters from Ghost- Spider: Exile the top card of your library. You may play that card this turn.| +Web-Warriors|Marvel's Spider-Man|203|U|{4}{G/W}|Creature - Spider Hero|4|3|When this creature enters, put a +1/+1 counter on each other creature you control.| +Spider-Man Noir|Marvel's Spider-Man|204|U|{4}{B}|Legendary Creature - Spider Human Hero|4|4|Menace$Whenever a creature you control attacks alone, put a +1/+1 counter on it. Then surveil X, where X is the number of counters on it.| +Spider-Man 2099|Marvel's Spider-Man|205|R|{U}{R}|Legendary Creature - Spider Human Hero|2|3|From the Future -- You can't cast Spider-Man 2099 during your first, second, or third turns of the game.$Double strike, vigilance$At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, Spider-Man 2099 deals damage equal to his power to any target.| +Multiversal Passage|Marvel's Spider-Man|206|R||Land|||As this land enters, choose a basic land type. Then you may pay 2 life. If you don't, it enters tapped.$This land is the chosen type.| +Spider-Punk|Marvel's Spider-Man|207|R|{1}{R}|Legendary Creature - Spider Human Hero|2|1|Riot$Other Spiders you control have riot.$Spells and abilities can't be countered.$Damage can't be prevented.| +Peter Parker|Marvel's Spider-Man|208|M|{1}{W}|Legendary Creature - Human Scientist Hero|0|1|When Peter Parker enters, create a 2/1 green Spider creature token with reach.${1}{G}{W}{U}: Transform Peter Parker. Activate only as a sorcery.| +Amazing Spider-Man|Marvel's Spider-Man|208|M|{1}{G}{W}{U}|Legendary Creature - Spider Human Hero|4|4|Vigilance, reach$Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}.| +Gwen Stacy|Marvel's Spider-Man|209|M|{1}{R}|Legendary Creature - Human Performer Hero|2|1|When Gwen Stacy enters, exile the top card of your library. You may play that card for as long as you control this creature.${2}{U}{R}{W}: Transform Gwen Stacy. Activate only as a sorcery.| +Ghost-Spider|Marvel's Spider-Man|209|M|{2}{U}{R}{W}|Legendary Creature - Spider Human Hero|2|1 Creature|Flying, vigilance, haste$Whenever you play a land from exile or cast a spell from exile, put a +1/+1 counter on Ghost-Spider.$Remove two counters from Ghost- Spider: Exile the top card of your library. You may play that card this turn.| +Spider-Punk|Marvel's Spider-Man|210|R|{1}{R}|Legendary Creature - Spider Human Hero|2|1|Riot$Other Spiders you control have riot.$Spells and abilities can't be countered.$Damage can't be prevented.| +Miles Morales|Marvel's Spider-Man|211|M|{1}{G}|Legendary Creature - Human Citizen Hero|1|2|When Miles Morales enters, put a +1/+1 counter on each of up to two target creatures.${3}{R}{G}{W}: Transform Miles Morales. Activate only as a sorcery.| +Ultimate Spider-Man|Marvel's Spider-Man|211|M|{3}{R}{G}{W}|Legendary Creature - Spider Human Hero|4|3|First strike, haste$Camouflage -- {2}: Put a +1/+1 counter on Ultimate Spider-Man. He gains hexproof and becomes colorless until end of turn.$Whenever you attack, double the number of each kind of counter on each Spider and legendary creature you control.| +Spider-Man 2099|Marvel's Spider-Man|216|R|{U}{R}|Legendary Creature - Spider Human Hero|2|3|From the Future -- You can't cast Spider-Man 2099 during your first, second, or third turns of the game.$Double strike, vigilance$At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, Spider-Man 2099 deals damage equal to his power to any target.| +Symbiote Spider-Man|Marvel's Spider-Man|217|R|{2}{U/B}|Legendary Creature - Symbiote Spider Hero|2|4|Whenever this creature deals combat damage to a player, look at that many cards from the top of your library. Put one of them into your hand and the rest into your graveyard.$Find New Host -- {2}{U/B}, Exile this card from your graveyard: Put a +1/+1 counter on target creature you control. It gains this card's other abilities. Activate only as a sorcery.| +Origin of Spider-Man|Marvel's Spider-Man|218|R|{1}{W}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Create a 2/1 green Spider creature token with reach.$II -- Put a +1/+1 counter on target creature you control. It becomes a legendary Spider Hero in addition to its other types.$III -- Target creature you control gains double strike until end of turn.| +Doctor Octopus, Master Planner|Marvel's Spider-Man|228|M|{5}{U}{B}|Legendary Creature - Human Scientist Villain|4|8|Other Villains you control get +2/+2.$Your maximum hand size is eight.$At the beginning of your end step, if you have fewer than eight cards in hand, draw cards equal to the difference.| Peter Parker|Marvel's Spider-Man|232|M|{1}{W}|Legendary Creature - Human Scientist Hero|0|1|When Peter Parker enters, create a 2/1 green Spider creature token with reach.${1}{G}{W}{U}: Transform Peter Parker. Activate only as a sorcery.| Amazing Spider-Man|Marvel's Spider-Man|232|M|{1}{G}{W}{U}|Legendary Creature - Spider Human Hero|4|4|Vigilance, reach$Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}.| +Miles Morales|Marvel's Spider-Man|234|M|{1}{G}|Legendary Creature - Human Citizen Hero|1|2|When Miles Morales enters, put a +1/+1 counter on each of up to two target creatures.${3}{R}{G}{W}: Transform Miles Morales. Activate only as a sorcery.| +Ultimate Spider-Man|Marvel's Spider-Man|234|M|{3}{R}{G}{W}|Legendary Creature - Spider Human Hero|4|3|First strike, haste$Camouflage -- {2}: Put a +1/+1 counter on Ultimate Spider-Man. He gains hexproof and becomes colorless until end of turn.$Whenever you attack, double the number of each kind of counter on each Spider and legendary creature you control.| +Anti-Venom, Horrifying Healer|Marvel's Spider-Man|244|M|{W}{W}{W}{W}{W}|Legendary Creature - Symbiote Hero|5|5|When Anti-Venom enters, if he was cast, return target creature card from your graveyard to the battlefield. If damage would be dealt to Anti-Venom, prevent that damage and put that many +1/+1 counters on him.| +Iron Spider, Stark Upgrade|Marvel's Spider-Man|279|R|{3}|Legendary Artifact Creature - Spider Hero|2|3|Vigilance${T}: Put a +1/+1 counter on each artifact creature and/or Vehicle you control.${2}, Remove two +1/+1 counters from among artifacts you control: Draw a card.|