From f03a410b9e808e5aeeabea8ad6c18255fd865e10 Mon Sep 17 00:00:00 2001 From: Raphael-Schulz <72562197+Raphael-Schulz@users.noreply.github.com> Date: Sat, 28 Aug 2021 21:55:41 +0200 Subject: [PATCH] [AFR] Implemented Xanathar, Guild Kingpin (#8045) * [AFR] Implemented Xanathar, Guild Kingpin Co-authored-by: Oleg Agafonov --- .../src/mage/cards/c/ConspicuousSnoop.java | 3 +- .../src/mage/cards/c/CourserOfKruphix.java | 3 +- .../src/mage/cards/e/ElshaOfTheInfinite.java | 9 +- .../src/mage/cards/g/GaleaKindlerOfHope.java | 2 +- Mage.Sets/src/mage/cards/g/GarruksHorde.java | 3 +- .../src/mage/cards/m/MelekIzzetParagon.java | 7 +- Mage.Sets/src/mage/cards/m/MysticForge.java | 3 +- .../src/mage/cards/o/OracleOfMulDaya.java | 3 +- .../src/mage/cards/p/PrecognitionField.java | 3 +- .../src/mage/cards/r/RadhaHeartOfKeld.java | 7 +- Mage.Sets/src/mage/cards/r/RangerClass.java | 3 +- Mage.Sets/src/mage/cards/r/Realmwalker.java | 3 +- Mage.Sets/src/mage/cards/v/VergeRangers.java | 3 +- .../mage/cards/v/VivienMonstersAdvocate.java | 2 +- .../mage/cards/v/VizierOfTheMenagerie.java | 2 +- .../mage/cards/x/XanatharGuildKingpin.java | 185 ++++++++++++++++++ .../sets/AdventuresInTheForgottenRealms.java | 1 + .../single/afr/XanatharGuildKingpinTest.java | 61 ++++++ .../LookAtTopCardOfLibraryAnyTimeEffect.java | 78 ++++++-- ...AtTopCardOfLibraryAnyTimeTargetEffect.java | 23 +++ .../continuous/PlayTheTopCardEffect.java | 84 ++++++-- .../PlayTheTopCardTargetEffect.java | 22 +++ .../src/main/java/mage/constants/Outcome.java | 2 +- .../java/mage/constants/TargetController.java | 3 +- .../card/CardOnTopOfLibraryPredicate.java | 17 +- 25 files changed, 477 insertions(+), 55 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/x/XanatharGuildKingpin.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/afr/XanatharGuildKingpinTest.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeTargetEffect.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardTargetEffect.java diff --git a/Mage.Sets/src/mage/cards/c/ConspicuousSnoop.java b/Mage.Sets/src/mage/cards/c/ConspicuousSnoop.java index a3ef5dfbb9..a790c80537 100644 --- a/Mage.Sets/src/mage/cards/c/ConspicuousSnoop.java +++ b/Mage.Sets/src/mage/cards/c/ConspicuousSnoop.java @@ -9,6 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.FilterCard; import java.util.UUID; @@ -36,7 +37,7 @@ public final class ConspicuousSnoop extends CardImpl { this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect())); // You may cast Goblin spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); // As long as the top card of your library is a Goblin card, Conspicuous Snoop has all activated abilities of that card. this.addAbility(new SimpleStaticAbility(new GainActivatedAbilitiesOfTopCardEffect(filter.copy().withMessage("a Goblin card")))); diff --git a/Mage.Sets/src/mage/cards/c/CourserOfKruphix.java b/Mage.Sets/src/mage/cards/c/CourserOfKruphix.java index 3dd49b3444..b24868717f 100644 --- a/Mage.Sets/src/mage/cards/c/CourserOfKruphix.java +++ b/Mage.Sets/src/mage/cards/c/CourserOfKruphix.java @@ -10,6 +10,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.filter.common.FilterLandCard; @@ -34,7 +35,7 @@ public final class CourserOfKruphix extends CardImpl { this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect())); // You may play lands from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); // Whenever a land enters the battlefield under your control, you gain 1 life. this.addAbility(new EntersBattlefieldControlledTriggeredAbility(new GainLifeEffect(1), StaticFilters.FILTER_LAND_A)); diff --git a/Mage.Sets/src/mage/cards/e/ElshaOfTheInfinite.java b/Mage.Sets/src/mage/cards/e/ElshaOfTheInfinite.java index c400fc2d8c..3c9dde86ab 100644 --- a/Mage.Sets/src/mage/cards/e/ElshaOfTheInfinite.java +++ b/Mage.Sets/src/mage/cards/e/ElshaOfTheInfinite.java @@ -9,10 +9,7 @@ import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; import mage.abilities.keyword.ProwessAbility; 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.constants.*; import mage.filter.FilterCard; import mage.filter.common.FilterNonlandCard; import mage.filter.predicate.Predicates; @@ -29,7 +26,7 @@ public final class ElshaOfTheInfinite extends CardImpl { static { filter.add(Predicates.not(CardType.CREATURE.getPredicate())); - filter.add(CardOnTopOfLibraryPredicate.instance); + filter.add(CardOnTopOfLibraryPredicate.YOUR); } public ElshaOfTheInfinite(UUID ownerId, CardSetInfo setInfo) { @@ -48,7 +45,7 @@ public final class ElshaOfTheInfinite extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast noncreature spells from the top of your library. If you cast a spell this way, you may cast it as though it had flash. - Ability ability = new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false)); + Ability ability = new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false)); ability.addEffect(new CastAsThoughItHadFlashAllEffect( Duration.WhileOnBattlefield, filter ).setText("If you cast a spell this way, you may cast it as though it had flash.")); diff --git a/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java b/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java index 7bf04c3d3f..b5d449e8e7 100644 --- a/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java +++ b/Mage.Sets/src/mage/cards/g/GaleaKindlerOfHope.java @@ -55,7 +55,7 @@ public final class GaleaKindlerOfHope extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast Aura and Equipment spells from the top of your library. When you cast an Equipment spell this way, it gains "When this Equipment enters the battlefield, attach it to target creature you control." - Ability ability = new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false)); + Ability ability = new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false)); ability.addEffect(new InfoEffect("When you cast an Equipment spell this way, it gains " + "\"When this Equipment enters the battlefield, attach it to target creature you control.\"")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/g/GarruksHorde.java b/Mage.Sets/src/mage/cards/g/GarruksHorde.java index 37cb695491..7b00eca127 100644 --- a/Mage.Sets/src/mage/cards/g/GarruksHorde.java +++ b/Mage.Sets/src/mage/cards/g/GarruksHorde.java @@ -9,6 +9,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.FilterCard; import mage.filter.common.FilterCreatureCard; @@ -35,7 +36,7 @@ public final class GarruksHorde extends CardImpl { this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect())); // You may cast creature spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); } private GarruksHorde(final GarruksHorde card) { diff --git a/Mage.Sets/src/mage/cards/m/MelekIzzetParagon.java b/Mage.Sets/src/mage/cards/m/MelekIzzetParagon.java index c28d833729..6cdd29c006 100644 --- a/Mage.Sets/src/mage/cards/m/MelekIzzetParagon.java +++ b/Mage.Sets/src/mage/cards/m/MelekIzzetParagon.java @@ -9,10 +9,7 @@ import mage.abilities.effects.common.continuous.PlayTheTopCardEffect; import mage.abilities.effects.common.continuous.PlayWithTheTopCardRevealedEffect; 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.constants.*; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.game.Game; @@ -49,7 +46,7 @@ public final class MelekIzzetParagon extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayWithTheTopCardRevealedEffect())); // You may cast instant and sorcery spells from the top of your library. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(filter, false))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(TargetController.YOU, filter, false))); // Whenever you cast an instant or sorcery spell from your library, copy it. You may choose new targets for the copy. this.addAbility(new MelekIzzetParagonTriggeredAbility()); diff --git a/Mage.Sets/src/mage/cards/m/MysticForge.java b/Mage.Sets/src/mage/cards/m/MysticForge.java index 1975c5d34b..09ad165fa8 100644 --- a/Mage.Sets/src/mage/cards/m/MysticForge.java +++ b/Mage.Sets/src/mage/cards/m/MysticForge.java @@ -12,6 +12,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.common.FilterNonlandCard; @@ -43,7 +44,7 @@ public final class MysticForge extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast artifact spells and colorless spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); // {T}, Pay 1 life: Exile the top card of your library. Ability ability = new SimpleActivatedAbility(new MysticForgeExileEffect(), new TapSourceCost()); diff --git a/Mage.Sets/src/mage/cards/o/OracleOfMulDaya.java b/Mage.Sets/src/mage/cards/o/OracleOfMulDaya.java index 3e077c7696..a5a6bc5d02 100644 --- a/Mage.Sets/src/mage/cards/o/OracleOfMulDaya.java +++ b/Mage.Sets/src/mage/cards/o/OracleOfMulDaya.java @@ -10,6 +10,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.FilterCard; import mage.filter.common.FilterLandCard; @@ -39,7 +40,7 @@ public final class OracleOfMulDaya extends CardImpl { this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect())); // You may play lands from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); } private OracleOfMulDaya(final OracleOfMulDaya card) { diff --git a/Mage.Sets/src/mage/cards/p/PrecognitionField.java b/Mage.Sets/src/mage/cards/p/PrecognitionField.java index 3f9ac0ef0d..1b3e999ac9 100644 --- a/Mage.Sets/src/mage/cards/p/PrecognitionField.java +++ b/Mage.Sets/src/mage/cards/p/PrecognitionField.java @@ -12,6 +12,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.TargetController; import mage.constants.Zone; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; @@ -41,7 +42,7 @@ public final class PrecognitionField extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast instant and sorcery spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); // {3}: Exile the top card of your library. this.addAbility(new SimpleActivatedAbility(new PrecognitionFieldExileEffect(), new GenericManaCost(3))); diff --git a/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java b/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java index 912e8e0023..f2e6fb023f 100644 --- a/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java +++ b/Mage.Sets/src/mage/cards/r/RadhaHeartOfKeld.java @@ -16,10 +16,7 @@ import mage.abilities.hint.common.MyTurnHint; import mage.abilities.keyword.FirstStrikeAbility; 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.constants.*; import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.filter.common.FilterLandCard; @@ -51,7 +48,7 @@ public final class RadhaHeartOfKeld extends CardImpl { // You may look at the top card of your library any time, and you may play lands from the top of your library. LookAtTopCardOfLibraryAnyTimeEffect lookEffect = new LookAtTopCardOfLibraryAnyTimeEffect(); lookEffect.setText("You may look at the top card of your library any time"); - PlayTheTopCardEffect playEffect = new PlayTheTopCardEffect(filter, false); + PlayTheTopCardEffect playEffect = new PlayTheTopCardEffect(TargetController.YOU, filter, false); playEffect.setText(", and you may play lands from the top of your library"); SimpleStaticAbility lookAndPlayAbility = new SimpleStaticAbility(lookEffect); diff --git a/Mage.Sets/src/mage/cards/r/RangerClass.java b/Mage.Sets/src/mage/cards/r/RangerClass.java index 19e83d4256..ee3fa76665 100644 --- a/Mage.Sets/src/mage/cards/r/RangerClass.java +++ b/Mage.Sets/src/mage/cards/r/RangerClass.java @@ -15,6 +15,7 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import mage.constants.TargetController; import mage.counters.CounterType; import mage.filter.FilterCard; import mage.filter.common.FilterCreatureCard; @@ -61,7 +62,7 @@ public final class RangerClass extends CardImpl { // You may cast creature spells from the top of your library. this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect( - new PlayTheTopCardEffect(filter, false), 3 + new PlayTheTopCardEffect(TargetController.YOU, filter, false), 3 ))); } diff --git a/Mage.Sets/src/mage/cards/r/Realmwalker.java b/Mage.Sets/src/mage/cards/r/Realmwalker.java index 6f57551358..75fc454b9a 100644 --- a/Mage.Sets/src/mage/cards/r/Realmwalker.java +++ b/Mage.Sets/src/mage/cards/r/Realmwalker.java @@ -12,6 +12,7 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.SubType; +import mage.constants.TargetController; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.mageobject.ChosenSubtypePredicate; @@ -45,7 +46,7 @@ public final class Realmwalker extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast creature spells of the chosen type from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); } private Realmwalker(final Realmwalker card) { diff --git a/Mage.Sets/src/mage/cards/v/VergeRangers.java b/Mage.Sets/src/mage/cards/v/VergeRangers.java index a3a8b11fec..b1026649ec 100644 --- a/Mage.Sets/src/mage/cards/v/VergeRangers.java +++ b/Mage.Sets/src/mage/cards/v/VergeRangers.java @@ -10,6 +10,7 @@ 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.FilterCard; import mage.filter.StaticFilters; @@ -58,7 +59,7 @@ class VergeRangersEffect extends PlayTheTopCardEffect { private static final FilterCard filter = new FilterLandCard("play lands"); public VergeRangersEffect() { - super(filter, false); + super(TargetController.YOU, filter, false); staticText = "As long as an opponent controls more lands than you, you may play lands from the top of your library"; } diff --git a/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java b/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java index cd715c1990..1985e62cf9 100644 --- a/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java +++ b/Mage.Sets/src/mage/cards/v/VivienMonstersAdvocate.java @@ -52,7 +52,7 @@ public final class VivienMonstersAdvocate extends CardImpl { this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast creature spells from the top of your library. - this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false))); + this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(TargetController.YOU, filter, false))); // +1: Create a 3/3 green Beast creature token. Put your choice of a vigilance // counter, a reach counter, or a trample counter on it. diff --git a/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java b/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java index 05c575f963..89dc665a90 100644 --- a/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java +++ b/Mage.Sets/src/mage/cards/v/VizierOfTheMenagerie.java @@ -37,7 +37,7 @@ public final class VizierOfTheMenagerie extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new LookAtTopCardOfLibraryAnyTimeEffect())); // You may cast creature spells from the top of your library. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(filter, false))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(TargetController.YOU, filter, false))); // You may spend mana as though it were mana of any type to cast creature spells. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new VizierOfTheMenagerieManaEffect())); diff --git a/Mage.Sets/src/mage/cards/x/XanatharGuildKingpin.java b/Mage.Sets/src/mage/cards/x/XanatharGuildKingpin.java new file mode 100644 index 0000000000..5fe1b5ac9d --- /dev/null +++ b/Mage.Sets/src/mage/cards/x/XanatharGuildKingpin.java @@ -0,0 +1,185 @@ +package mage.cards.x; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.AsThoughManaEffect; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeTargetEffect; +import mage.abilities.effects.common.continuous.PlayTheTopCardTargetEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.ManaPoolItem; +import mage.players.Player; +import mage.target.common.TargetOpponent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author raphael-schulz + */ +public final class XanatharGuildKingpin extends CardImpl { + + public XanatharGuildKingpin(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}{B}"); + addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.BEHOLDER); + + this.power = new MageInt(5); + this.toughness = new MageInt(6); + + // At the beginning of your upkeep, choose target opponent. Until end of turn, that player can’t cast spells, you may look at the top card of their library any time, you may play the top card of their library, and you may spend mana as though it were mana of any color to cast spells this way. + Ability ability = new BeginningOfUpkeepTriggeredAbility( + Zone.BATTLEFIELD, new XanatharGuildKingpinRuleModifyingEffect() + .setText("choose target opponent. Until end of turn, that player can't cast spells,"), + TargetController.YOU, false + ); + ability.addEffect(new LookAtTopCardOfLibraryAnyTimeTargetEffect(Duration.EndOfTurn) + .setText(" you may look at the top card of their library any time,")); + ability.addEffect(new PlayTheTopCardTargetEffect() + .setText(" you may play the top card of their library,")); + ability.addEffect(new XanatharGuildKingpinSpendManaAsAnyColorOneShotEffect() + .setText(" and you may spend mana as thought it were mana of any color to cast spells this way")); + ability.addCustomOutcome(Outcome.PreventCast); + ability.addTarget(new TargetOpponent()); + this.addAbility(ability); + } + + private XanatharGuildKingpin(final XanatharGuildKingpin card) { + super(card); + } + + @Override + public XanatharGuildKingpin copy() { + return new XanatharGuildKingpin(this); + } +} + +class XanatharGuildKingpinRuleModifyingEffect extends ContinuousRuleModifyingEffectImpl { + + public XanatharGuildKingpinRuleModifyingEffect() { + super(Duration.EndOfTurn, Outcome.Benefit); + } + + private XanatharGuildKingpinRuleModifyingEffect(final XanatharGuildKingpinRuleModifyingEffect effect) { + super(effect); + } + + @Override + public XanatharGuildKingpinRuleModifyingEffect copy() { + return new XanatharGuildKingpinRuleModifyingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public String getInfoMessage(Ability source, GameEvent event, Game game) { + Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source)); + MageObject mageObject = game.getObject(source.getSourceId()); + if (targetPlayer != null && mageObject != null) { + return "This turn you can't cast spells" + + " (" + mageObject.getLogName() + ')'; + } + return null; + } + + @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) { + return event.getPlayerId().equals(getTargetPointer().getFirst(game, source)); + } +} + +class XanatharGuildKingpinSpendManaAsAnyColorOneShotEffect extends OneShotEffect { + + public XanatharGuildKingpinSpendManaAsAnyColorOneShotEffect() { + super(Outcome.Benefit); + } + + private XanatharGuildKingpinSpendManaAsAnyColorOneShotEffect(final XanatharGuildKingpinSpendManaAsAnyColorOneShotEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Card topCard = game.getPlayer(source.getFirstTarget()).getLibrary().getFromTop(game); + if (topCard == null) { + return false; + } + + int zcc = game.getState().getZoneChangeCounter(topCard.getId()); + game.addEffect(new SpendManaAsAnyColorToCastTopOfLibraryTargetEffect().setTargetPointer(new FixedTarget(topCard.getId(), zcc)), source); + return true; + } + + @Override + public XanatharGuildKingpinSpendManaAsAnyColorOneShotEffect copy() { + return new XanatharGuildKingpinSpendManaAsAnyColorOneShotEffect(this); + } + +} + +class SpendManaAsAnyColorToCastTopOfLibraryTargetEffect extends AsThoughEffectImpl implements AsThoughManaEffect { + + public SpendManaAsAnyColorToCastTopOfLibraryTargetEffect() { + super(AsThoughEffectType.SPEND_OTHER_MANA, Duration.EndOfTurn, Outcome.Benefit); + } + + public SpendManaAsAnyColorToCastTopOfLibraryTargetEffect(final SpendManaAsAnyColorToCastTopOfLibraryTargetEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public SpendManaAsAnyColorToCastTopOfLibraryTargetEffect copy() { + return new SpendManaAsAnyColorToCastTopOfLibraryTargetEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + FixedTarget fixedTarget = ((FixedTarget) getTargetPointer()); + UUID targetId = CardUtil.getMainCardId(game, fixedTarget.getTarget()); + + Card topCard = game.getPlayer(source.getFirstTarget()).getLibrary().getFromTop(game); + if (topCard == null) { + return false; + } + + // If top card of target opponent's library changed, discard the current ContinuousEffect and create a new one + if (!topCard.getId().equals(targetId) && canLookAtNextTopLibraryCard(game) && !this.isDiscarded()) { + int zcc = game.getState().getZoneChangeCounter(topCard.getId()); + game.addEffect(new SpendManaAsAnyColorToCastTopOfLibraryTargetEffect().setTargetPointer(new FixedTarget(topCard.getId(), zcc)), source); + this.discard(); + } + return source.isControlledBy(affectedControllerId) + && Objects.equals(objectId, targetId) + && game.getState().getZoneChangeCounter(objectId) <= fixedTarget.getZoneChangeCounter() + 1 + && (game.getState().getZone(objectId) == Zone.STACK || game.getState().getZone(objectId) == Zone.LIBRARY); + } + + @Override + public ManaType getAsThoughManaType(ManaType manaType, ManaPoolItem mana, UUID affectedControllerId, Ability source, Game game) { + return mana.getFirstAvailable(); + } +} diff --git a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java index 78bc4fba4e..f4a444f5b5 100644 --- a/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java +++ b/Mage.Sets/src/mage/sets/AdventuresInTheForgottenRealms.java @@ -281,6 +281,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet { cards.add(new SetCardInfo("Wish", 166, Rarity.RARE, mage.cards.w.Wish.class)); cards.add(new SetCardInfo("Wizard Class", 81, Rarity.UNCOMMON, mage.cards.w.WizardClass.class)); cards.add(new SetCardInfo("Wizard's Spellbook", 82, Rarity.RARE, mage.cards.w.WizardsSpellbook.class)); + cards.add(new SetCardInfo("Xanathar, Guild Kingpin", 239, Rarity.MYTHIC, mage.cards.x.XanatharGuildKingpin.class)); cards.add(new SetCardInfo("Xorn", 167, Rarity.RARE, mage.cards.x.Xorn.class)); cards.add(new SetCardInfo("You Come to a River", 83, Rarity.COMMON, mage.cards.y.YouComeToARiver.class)); cards.add(new SetCardInfo("You Come to the Gnoll Camp", 168, Rarity.COMMON, mage.cards.y.YouComeToTheGnollCamp.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/afr/XanatharGuildKingpinTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/afr/XanatharGuildKingpinTest.java new file mode 100644 index 0000000000..09701105b4 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/afr/XanatharGuildKingpinTest.java @@ -0,0 +1,61 @@ +package org.mage.test.cards.single.afr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class XanatharGuildKingpinTest extends CardTestPlayerBase { + + @Test + public void test_Play() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromLibrary(playerB); + skipInitShuffling(); + + // At the beginning of your upkeep, choose target opponent. + // Until end of turn, that player can’t cast spells, you may look at the top card of their library any time, + // you may play the top card of their library, and you may spend mana as though it were mana of any color + // to cast spells this way. + addCard(Zone.BATTLEFIELD, playerA, "Xanathar, Guild Kingpin"); + // + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2); + // + addCard(Zone.HAND, playerA, "Thunderbolt"); + // + addCard(Zone.HAND, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); + // + addCard(Zone.LIBRARY, playerB, "Mountain"); + addCard(Zone.LIBRARY, playerB, "Lightning Bolt"); + addCard(Zone.LIBRARY, playerB, "Grizzly Bears"); + + // activate on opponent + addTarget(playerA, playerB); + + // B can't cast spells + checkPlayableAbility("B can't cast", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Lightning Bolt", false); + // A can cast own and from B library + checkPlayableAbility("A can cast own", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Thunderbolt", true); + checkPlayableAbility("A can cast from B", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Grizzly Bears", true); + + // cast from B and try another one with any color and full stack + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}", 2); // pay for {G} as any + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", null, "Grizzly Bears"); + addTarget(playerA, playerB); // bolt + checkStackSize("multi cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2); + + // B can cast again + checkPlayableAbility("B can cast", 2, PhaseStep.PRECOMBAT_MAIN, playerB, "Cast Lightning Bolt", true); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java index 3c1ae950d5..281a6cead4 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeEffect.java @@ -4,48 +4,102 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.Card; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; +import mage.constants.*; import mage.game.Game; import mage.players.Player; +import mage.util.CardUtil; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; /** * @author TheElk801 */ public class LookAtTopCardOfLibraryAnyTimeEffect extends ContinuousEffectImpl { + private final TargetController targetLibrary; + public LookAtTopCardOfLibraryAnyTimeEffect() { - super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); - staticText = "You may look at the top card of your library any time."; + this(TargetController.YOU, Duration.WhileOnBattlefield); } - private LookAtTopCardOfLibraryAnyTimeEffect(final LookAtTopCardOfLibraryAnyTimeEffect effect) { + public LookAtTopCardOfLibraryAnyTimeEffect(TargetController targetLibrary, Duration duration) { + super(duration, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); + this.targetLibrary = targetLibrary; + + String libInfo; + switch (this.targetLibrary) { + case YOU: + libInfo = "your library"; + break; + case OPPONENT: + libInfo = "opponents libraries"; + break; + case SOURCE_TARGETS: + libInfo = "target player's library"; + break; + default: + throw new IllegalArgumentException("Unknown target library type: " + targetLibrary); + } + staticText = duration.toString().isEmpty() ? "" : duration + " you may look at the top card of " + libInfo + " any time."; + } + + protected LookAtTopCardOfLibraryAnyTimeEffect(final LookAtTopCardOfLibraryAnyTimeEffect effect) { super(effect); + this.targetLibrary = effect.targetLibrary; } @Override public boolean apply(Game game, Ability source) { if (game.inCheckPlayableState()) { // Ignored - see https://github.com/magefree/mage/issues/6994 return false; - } + } Player controller = game.getPlayer(source.getControllerId()); if (controller == null) { return false; } - Card topCard = controller.getLibrary().getFromTop(game); - if (topCard == null) { + if (!canLookAtNextTopLibraryCard(game)) { return false; } MageObject obj = source.getSourceObject(game); if (obj == null) { return false; } - if (!canLookAtNextTopLibraryCard(game)) { + + Set needPlayers = new HashSet<>(); + switch (this.targetLibrary) { + case YOU: { + needPlayers.add(source.getControllerId()); + break; + } + case OPPONENT: { + needPlayers.addAll(game.getOpponents(source.getControllerId())); + break; + } + case SOURCE_TARGETS: { + needPlayers.addAll(CardUtil.getAllSelectedTargets(source, game)); + break; + } + } + + Set needCards = new HashSet<>(); + needPlayers.stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .map(player -> player.getLibrary().getFromTop(game)) + .filter(Objects::nonNull) + .forEach(needCards::add); + if (needCards.isEmpty()) { return false; } - controller.lookAtCards("Top card of " + obj.getIdName() + " controller's library", topCard, game); + + // all fine, can show top card + needCards.forEach(topCard -> { + Player owner = game.getPlayer(topCard.getOwnerId()); + controller.lookAtCards(String.format("%s: top card of %s", obj.getName(), owner == null ? "error" : owner.getName()), topCard, game); + }); return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeTargetEffect.java new file mode 100644 index 0000000000..4af3cf4d5d --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtTopCardOfLibraryAnyTimeTargetEffect.java @@ -0,0 +1,23 @@ +package mage.abilities.effects.common.continuous; + +import mage.constants.Duration; +import mage.constants.TargetController; + +/** + * @author JayDi85 + */ +public class LookAtTopCardOfLibraryAnyTimeTargetEffect extends LookAtTopCardOfLibraryAnyTimeEffect { + + public LookAtTopCardOfLibraryAnyTimeTargetEffect(Duration duration) { + super(TargetController.SOURCE_TARGETS, duration); + } + + private LookAtTopCardOfLibraryAnyTimeTargetEffect(final LookAtTopCardOfLibraryAnyTimeTargetEffect effect) { + super(effect); + } + + @Override + public LookAtTopCardOfLibraryAnyTimeTargetEffect copy() { + return new LookAtTopCardOfLibraryAnyTimeTargetEffect(this); + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java index a9719c54bb..14049f882e 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java @@ -6,11 +6,14 @@ import mage.cards.Card; import mage.constants.AsThoughEffectType; import mage.constants.Duration; import mage.constants.Outcome; +import mage.constants.TargetController; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; +import mage.util.CardUtil; import java.util.Locale; +import java.util.Objects; import java.util.UUID; /** @@ -19,20 +22,44 @@ import java.util.UUID; public class PlayTheTopCardEffect extends AsThoughEffectImpl { private final FilterCard filter; + private final TargetController targetLibrary; // can play card or can play lands/cast spells, see two modes below private final boolean canPlayCardOnly; + /** + * Support targets, use TargetController.SOURCE_TARGETS + */ public PlayTheTopCardEffect() { - this(new FilterCard("play lands and cast spells"), false); + this(TargetController.YOU); } - public PlayTheTopCardEffect(FilterCard filter, boolean canPlayCardOnly) { + public PlayTheTopCardEffect(TargetController targetLibrary) { + this(targetLibrary, new FilterCard("play lands and cast spells"), false); + } + + public PlayTheTopCardEffect(TargetController targetLibrary, FilterCard filter, boolean canPlayCardOnly) { super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); this.filter = filter; + this.targetLibrary = targetLibrary; this.canPlayCardOnly = canPlayCardOnly; - this.staticText = "You may " + filter.getMessage() + " from the top of your library"; + + String libInfo; + switch (this.targetLibrary) { + case YOU: + libInfo = "your library"; + break; + case OPPONENT: + libInfo = "opponents libraries"; + break; + case SOURCE_TARGETS: + libInfo = "target player's library"; + break; + default: + throw new IllegalArgumentException("Unknown target library type: " + targetLibrary); + } + this.staticText = "You may " + filter.getMessage() + " from the top of " + libInfo; // verify check: if you see "card" text in the rules then use card mode // (there aren't any real cards after oracle update, but can be added in the future) @@ -44,6 +71,7 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl { public PlayTheTopCardEffect(final PlayTheTopCardEffect effect) { super(effect); this.filter = effect.filter; + this.targetLibrary = effect.targetLibrary; this.canPlayCardOnly = effect.canPlayCardOnly; } @@ -71,7 +99,7 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl { } if (this.canPlayCardOnly) { - // check whole card intead part + // check whole card instead part cardToCheck = cardToCheck.getMainCard(); } @@ -80,16 +108,50 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl { return false; } - // must be your card - Player player = game.getPlayer(cardToCheck.getOwnerId()); - if (player == null || !player.getId().equals(affectedControllerId)) { + Player cardOwner = game.getPlayer(cardToCheck.getOwnerId()); + Player controller = game.getPlayer(source.getControllerId()); + if (cardOwner == null || controller == null) { return false; } - // must be from your library - Card topCard = player.getLibrary().getFromTop(game); - if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) { - return false; + // must be your or opponents library + switch (this.targetLibrary) { + case YOU: { + Card topCard = controller.getLibrary().getFromTop(game); + if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) { + return false; + } + break; + } + + case OPPONENT: { + if (!game.getOpponents(controller.getId()).contains(cardOwner.getId())) { + return false; + } + Card topCard = cardOwner.getLibrary().getFromTop(game); + if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) { + return false; + } + break; + } + + case SOURCE_TARGETS: { + UUID needCardId = cardToCheck.getMainCard().getId(); + if (CardUtil.getAllSelectedTargets(source, game).stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .noneMatch(player -> { + Card topCard = player.getLibrary().getFromTop(game); + return topCard != null && topCard.getId().equals(needCardId); + })) { + return false; + } + break; + } + + default: { + return false; + } } // can't cast without mana cost diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardTargetEffect.java new file mode 100644 index 0000000000..64be57feab --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardTargetEffect.java @@ -0,0 +1,22 @@ +package mage.abilities.effects.common.continuous; + +import mage.constants.TargetController; + +/** + * @author JayDi85 + */ +public class PlayTheTopCardTargetEffect extends PlayTheTopCardEffect { + + public PlayTheTopCardTargetEffect() { + super(TargetController.SOURCE_TARGETS); + } + + public PlayTheTopCardTargetEffect(final PlayTheTopCardTargetEffect effect) { + super(effect); + } + + @Override + public PlayTheTopCardTargetEffect copy() { + return new PlayTheTopCardTargetEffect(this); + } +} diff --git a/Mage/src/main/java/mage/constants/Outcome.java b/Mage/src/main/java/mage/constants/Outcome.java index 2fdba3d1ce..1f4e468121 100644 --- a/Mage/src/main/java/mage/constants/Outcome.java +++ b/Mage/src/main/java/mage/constants/Outcome.java @@ -28,7 +28,7 @@ public enum Outcome { PutManaInPool(true), Regenerate(true), PreventDamage(true), // TODO: check good or bad - PreventCast(false), // TODO: check good or bad + PreventCast(false), RedirectDamage(true), // TODO: check good or bad Tap(false), Transform(true), diff --git a/Mage/src/main/java/mage/constants/TargetController.java b/Mage/src/main/java/mage/constants/TargetController.java index ffe2b05a62..c827820bbd 100644 --- a/Mage/src/main/java/mage/constants/TargetController.java +++ b/Mage/src/main/java/mage/constants/TargetController.java @@ -25,7 +25,8 @@ public enum TargetController { OWNER, CONTROLLER_ATTACHED_TO, NEXT, - EACH_PLAYER; + EACH_PLAYER, + SOURCE_TARGETS; private final OwnerPredicate ownerPredicate; private final PlayerPredicate playerPredicate; diff --git a/Mage/src/main/java/mage/filter/predicate/card/CardOnTopOfLibraryPredicate.java b/Mage/src/main/java/mage/filter/predicate/card/CardOnTopOfLibraryPredicate.java index 6024ed0c90..f21aa55085 100644 --- a/Mage/src/main/java/mage/filter/predicate/card/CardOnTopOfLibraryPredicate.java +++ b/Mage/src/main/java/mage/filter/predicate/card/CardOnTopOfLibraryPredicate.java @@ -11,11 +11,24 @@ import mage.players.Player; */ public enum CardOnTopOfLibraryPredicate implements ObjectPlayerPredicate> { - instance; + YOUR, + ANY; @Override public boolean apply(ObjectPlayer input, Game game) { - Player player = game.getPlayer(input.getObject().getOwnerId()); + + Player player; + switch (this) { + case YOUR: + player = game.getPlayer(input.getPlayerId()); + break; + + case ANY: + default: + player = game.getPlayer(input.getObject().getOwnerId()); + break; + } + if (player == null) { return false; }