From ee3cab84ef8244f0417392d15dcc5b6895846a5c Mon Sep 17 00:00:00 2001 From: jimga150 Date: Wed, 31 Jul 2024 22:45:59 -0400 Subject: [PATCH] [BLC] Implement Fortune Teller's Talent (#12608) * Implement Fortune Teller's Talent * Remove outdated assert from PlayFromTopOfLibraryEffect and update comment --- .../mage/cards/f/FortuneTellersTalent.java | 95 +++++++++++++++++++ .../src/mage/sets/BloomburrowCommander.java | 1 + .../single/blc/FortuneTellersTalentTest.java | 88 +++++++++++++++++ .../PlayFromTopOfLibraryEffect.java | 7 +- 4 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/f/FortuneTellersTalent.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/blc/FortuneTellersTalentTest.java diff --git a/Mage.Sets/src/mage/cards/f/FortuneTellersTalent.java b/Mage.Sets/src/mage/cards/f/FortuneTellersTalent.java new file mode 100644 index 00000000000..b4e48d23c40 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FortuneTellersTalent.java @@ -0,0 +1,95 @@ +package mage.cards.f; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalAsThoughEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect; +import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect; +import mage.abilities.effects.common.continuous.PlayFromTopOfLibraryEffect; +import mage.abilities.effects.common.cost.SpellsCostReductionControllerEffect; +import mage.abilities.keyword.ClassLevelAbility; +import mage.abilities.keyword.ClassReminderAbility; +import mage.constants.SubType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.FilterCard; +import mage.filter.predicate.other.SpellCastFromAnywhereOtherThanHand; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.watchers.common.SpellsCastWatcher; + +/** + * + * @author jimga150 + */ +public final class FortuneTellersTalent extends CardImpl { + + private static final FilterCard nonHandSpellFilter = new FilterCard("Spells you cast from anywhere other than your hand"); + + static { + nonHandSpellFilter.add(SpellCastFromAnywhereOtherThanHand.instance); + } + + public FortuneTellersTalent(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}"); + + this.subtype.add(SubType.CLASS); + + // (Gain the next level as a sorcery to add its ability.) + this.addAbility(new ClassReminderAbility()); + + // You may look at the top card of your library any time. + this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect())); + + // {3}{U}: Level 2 + this.addAbility(new ClassLevelAbility(2, "{3}{U}")); + + // As long as you've cast a spell this turn, you may play cards from the top of your library. + Effect lv2Effect = new ConditionalAsThoughEffect( + new PlayFromTopOfLibraryEffect(), + FortuneTellersTalentCondition.instance + ); + lv2Effect.setText("As long as you've cast a spell this turn, you may play cards from the top of your library."); + this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(new SimpleStaticAbility(lv2Effect), 2))); + + // {2}{U}: Level 3 + this.addAbility(new ClassLevelAbility(3, "{2}{U}")); + + // Spells you cast from anywhere other than your hand cost {2} less to cast. + Ability lv3Ability = new SimpleStaticAbility(new SpellsCostReductionControllerEffect(nonHandSpellFilter, 2)); + this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(lv3Ability, 3))); + } + + private FortuneTellersTalent(final FortuneTellersTalent card) { + super(card); + } + + @Override + public FortuneTellersTalent copy() { + return new FortuneTellersTalent(this); + } +} + +// Based on LeapfrogCondition +enum FortuneTellersTalentCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + if (watcher == null) { + return false; + } + List spells = watcher.getSpellsCastThisTurn(source.getControllerId()); + return spells != null && spells + .stream() + .anyMatch(Objects::nonNull); + } +} diff --git a/Mage.Sets/src/mage/sets/BloomburrowCommander.java b/Mage.Sets/src/mage/sets/BloomburrowCommander.java index 96c30b5b37a..8e50ec073bf 100644 --- a/Mage.Sets/src/mage/sets/BloomburrowCommander.java +++ b/Mage.Sets/src/mage/sets/BloomburrowCommander.java @@ -104,6 +104,7 @@ public final class BloomburrowCommander extends ExpansionSet { cards.add(new SetCardInfo("Flubs, the Fool", 356, Rarity.MYTHIC, mage.cards.f.FlubsTheFool.class)); cards.add(new SetCardInfo("Forgotten Ancient", 217, Rarity.RARE, mage.cards.f.ForgottenAncient.class)); cards.add(new SetCardInfo("Forgotten Cave", 305, Rarity.COMMON, mage.cards.f.ForgottenCave.class)); + cards.add(new SetCardInfo("Fortune Teller's Talent", 14, Rarity.RARE, mage.cards.f.FortuneTellersTalent.class)); cards.add(new SetCardInfo("Game Trail", 306, Rarity.RARE, mage.cards.g.GameTrail.class)); cards.add(new SetCardInfo("Garruk's Packleader", 218, Rarity.UNCOMMON, mage.cards.g.GarruksPackleader.class)); cards.add(new SetCardInfo("Garruk's Uprising", 219, Rarity.UNCOMMON, mage.cards.g.GarruksUprising.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/blc/FortuneTellersTalentTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/blc/FortuneTellersTalentTest.java new file mode 100644 index 00000000000..f85a239c384 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/blc/FortuneTellersTalentTest.java @@ -0,0 +1,88 @@ +package org.mage.test.cards.single.blc; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class FortuneTellersTalentTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.f.FortuneTellersTalent Fortune Teller's Talent} {U} + * Enchantment — Class + * You may look at the top card of your library any time. + * {3}{U}: Level 2 + * As long as you’ve cast a spell this turn, you may play cards from the top of your library. + * {2}{U}: Level 3 + * Spells you cast from anywhere other than your hand cost {2} less to cast. + */ + private static final String fortuneTeller = "Fortune Teller's Talent"; + + private void assertClassLevel(String cardName, int level) { + Permanent permanent = getPermanent(cardName); + Assert.assertEquals( + cardName + " should be level " + level + + " but was level " + permanent.getClassLevel(), + level, permanent.getClassLevel() + ); + } + + @Test + public void test_PlayFromLibrary() { + skipInitShuffling(); + addCard(Zone.BATTLEFIELD, playerA, "Island", 14); + // {3}{U}: Instant: Return all attacking creatures to their owner’s hand. + addCard(Zone.LIBRARY, playerA, "Aetherize", 4); + addCard(Zone.HAND, playerA, fortuneTeller); + + int islandsUsed = 0; + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, fortuneTeller, true); + islandsUsed++; + +// checkPermanentTapped("Used " + islandsUsed + " Islands", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island", true, islandsUsed); + + // Level 2: As long as you’ve cast a spell this turn, you may play cards from the top of your library. + // (Fortune Teller counts) + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}{U}"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + islandsUsed += 4; + + checkPermanentTapped("Used " + islandsUsed + " Islands", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island", true, islandsUsed); + + checkHandCardCount("not in hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aetherize", 0); + checkPlayableAbility("from library", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Aetherize", true); + //This will cast from the top of the library + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aetherize", true); + islandsUsed += 4; + + checkPermanentTapped("Used " + islandsUsed + " Islands", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island", true, islandsUsed); + + // Level 3: Spells you cast from anywhere other than your hand cost {2} less to cast. + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{U}"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA); + islandsUsed += 3; + + checkHandCardCount("not in hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aetherize", 0); + checkPlayableAbility("from library", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Aetherize", true); + //This will cast from the top of the library + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aetherize", true); + islandsUsed += 2; // Should have had cost reduced by 2 + + checkPermanentTapped("Used " + islandsUsed + " Islands", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Island", true, islandsUsed); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertClassLevel(fortuneTeller, 3); + assertLibraryCount(playerA, "Aetherize", 2); + + } + +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayFromTopOfLibraryEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayFromTopOfLibraryEffect.java index 32a577fdf4a..47f63feedfa 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayFromTopOfLibraryEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayFromTopOfLibraryEffect.java @@ -31,17 +31,12 @@ public class PlayFromTopOfLibraryEffect extends AsThoughEffectImpl { } /** - * You may [play lands and/or cast spells, according to filter] from the top of your library + * You may [play lands/cast spells/play cards, according to filter] from the top of your library */ public PlayFromTopOfLibraryEffect(FilterCard filter) { super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit); this.filter = filter; this.staticText = "you may " + filter.getMessage() + " from the top of your library"; - - // verify check: this ability is to allow playing lands or casting spells, not playing a "card" - if (filter.getMessage().toLowerCase(Locale.ENGLISH).contains("card")) { - throw new IllegalArgumentException("Wrong code usage or wrong filter text: PlayTheTopCardEffect"); - } } protected PlayFromTopOfLibraryEffect(final PlayFromTopOfLibraryEffect effect) {