From dc1e94648b44a32d5eab8edf7e127c52ecf2a454 Mon Sep 17 00:00:00 2001 From: Vivian Greenslade Date: Sat, 9 Sep 2023 17:02:34 -0230 Subject: [PATCH] [BRC] Implement Kayla's Music Box (#11130) --- .../src/mage/cards/k/KaylasMusicBox.java | 169 ++++++++++++++++++ .../mage/sets/TheBrothersWarCommander.java | 1 + .../cards/single/brc/KaylasMusicBoxTest.java | 51 ++++++ 3 files changed, 221 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/k/KaylasMusicBox.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/brc/KaylasMusicBoxTest.java diff --git a/Mage.Sets/src/mage/cards/k/KaylasMusicBox.java b/Mage.Sets/src/mage/cards/k/KaylasMusicBox.java new file mode 100644 index 00000000000..c0fecd5b91b --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KaylasMusicBox.java @@ -0,0 +1,169 @@ +package mage.cards.k; + +import java.util.UUID; +import mage.constants.SuperType; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsThoughEffectImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; + +/** + * + * @author Xanderhall + */ +public final class KaylasMusicBox extends CardImpl { + + public KaylasMusicBox(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.supertype.add(SuperType.LEGENDARY); + + // {W}, {T}: Look at the top card of your library, then exile it face down. + Ability ability = new SimpleActivatedAbility(new KaylasMusicBoxExileEffect(), new ManaCostsImpl<>("{W}")); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + + // {T}: Until end of turn, you may play cards you own exiled with Kayla's Music Box. + this.addAbility(new SimpleActivatedAbility(new KaylasMusicBoxPlayFromExileEffect(), new TapSourceCost())); + } + + private KaylasMusicBox(final KaylasMusicBox card) { + super(card); + } + + @Override + public KaylasMusicBox copy() { + return new KaylasMusicBox(this); + } +} + + +class KaylasMusicBoxExileEffect extends OneShotEffect { + + KaylasMusicBoxExileEffect() { + super(Outcome.Benefit); + staticText = "look at the top card of your library, then exile it face down"; + } + + private KaylasMusicBoxExileEffect(final KaylasMusicBoxExileEffect effect) { + super(effect); + } + + @Override + public KaylasMusicBoxExileEffect copy() { + return new KaylasMusicBoxExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null || sourceObject == null) { + return false; + } + + Card card = controller.getLibrary().getFromTop(game); + if (card == null) { + return false; + } + + card.setFaceDown(true, game); + controller.lookAtCards(null, card, game); + + if (controller.moveCardsToExile(card, source, game, true, CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source))) { + card.setFaceDown(true, game); + // No other player may look at the face-down cards you own exiled with Kayla’s Music Box, even if another player takes control of it. + // (2022-10-14) + ContinuousEffect effect = new KaylasMusicBoxLookEffect(controller.getId()); + effect.setTargetPointer(new FixedTarget(card.getId(), game)); + game.addEffect(effect, source); + } + return true; + } + +} + +class KaylasMusicBoxLookEffect extends AsThoughEffectImpl { + + private final UUID authorizedPlayerId; + + public KaylasMusicBoxLookEffect(UUID authorizedPlayerId) { + super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit); + this.authorizedPlayerId = authorizedPlayerId; + staticText = "You may look at the cards exiled with {this}"; + } + + private KaylasMusicBoxLookEffect(final KaylasMusicBoxLookEffect effect) { + super(effect); + this.authorizedPlayerId = effect.authorizedPlayerId; + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public KaylasMusicBoxLookEffect copy() { + return new KaylasMusicBoxLookEffect(this); + } + + @Override + public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { + UUID cardId = getTargetPointer().getFirst(game, source); + if (cardId == null) { + this.discard(); // card is no longer in the origin zone, effect can be discarded + } + return affectedControllerId.equals(authorizedPlayerId) + && objectId.equals(cardId); + } + +} + +class KaylasMusicBoxPlayFromExileEffect extends AsThoughEffectImpl { + + KaylasMusicBoxPlayFromExileEffect() { + super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit); + staticText = "until end of turn, you may play cards you own exiled with {this}."; + } + + private KaylasMusicBoxPlayFromExileEffect(final KaylasMusicBoxPlayFromExileEffect effect) { + super(effect); + } + + @Override + public KaylasMusicBoxPlayFromExileEffect copy() { + return new KaylasMusicBoxPlayFromExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) { + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + if (exileZone == null || !exileZone.contains(sourceId)) { + return false; + } + CardUtil.makeCardPlayable(game, source, exileZone.get(sourceId, game), Duration.EndOfTurn, false); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/TheBrothersWarCommander.java b/Mage.Sets/src/mage/sets/TheBrothersWarCommander.java index b7057d8783c..4d2f21de7a2 100644 --- a/Mage.Sets/src/mage/sets/TheBrothersWarCommander.java +++ b/Mage.Sets/src/mage/sets/TheBrothersWarCommander.java @@ -87,6 +87,7 @@ public final class TheBrothersWarCommander extends ExpansionSet { cards.add(new SetCardInfo("Island", 31, Rarity.LAND, mage.cards.basiclands.Island.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Izzet Boilerworks", 188, Rarity.UNCOMMON, mage.cards.i.IzzetBoilerworks.class)); cards.add(new SetCardInfo("Jhoira, Weatherlight Captain", 126, Rarity.MYTHIC, mage.cards.j.JhoiraWeatherlightCaptain.class)); + cards.add(new SetCardInfo("Kayla's Music Box", 15, Rarity.RARE, mage.cards.k.KaylasMusicBox.class)); cards.add(new SetCardInfo("Liquimetal Torque", 145, Rarity.UNCOMMON, mage.cards.l.LiquimetalTorque.class)); cards.add(new SetCardInfo("Lithoform Engine", 146, Rarity.MYTHIC, mage.cards.l.LithoformEngine.class)); cards.add(new SetCardInfo("Losheel, Clockwork Scholar", 73, Rarity.RARE, mage.cards.l.LosheelClockworkScholar.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/brc/KaylasMusicBoxTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/brc/KaylasMusicBoxTest.java new file mode 100644 index 00000000000..8c7c93dc703 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/brc/KaylasMusicBoxTest.java @@ -0,0 +1,51 @@ +package org.mage.test.cards.single.brc; + +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +import mage.constants.PhaseStep; +import mage.constants.Zone; + +public class KaylasMusicBoxTest extends CardTestPlayerBase { + + private static final String BOX = "Kayla's Music Box"; + private static final String LION = "Silvercoat Lion"; + + /** + * {W}, {T}: Look at the top card of your library, then exile it face down. (You may look at it any time.) + * {T}: Until end of turn, you may play cards you own exiled with Kayla’s Music Box. + */ + @Test + public void testEffect() { + removeAllCardsFromLibrary(playerA); + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, LION, 2); + addCard(Zone.LIBRARY, playerA, "Mountain", 1); + addCard(Zone.LIBRARY, playerA, "Plains", 1); + addCard(Zone.LIBRARY, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, BOX); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + + // Player A activates the box, exiling lion + activateAbility(1, PhaseStep.END_TURN, playerA, "{W}, {T}"); + + // Player A draws mountain, activates box again to exile plains + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{W}, {T}"); + + // Player A activates the box on next turn, plays lion and plains + activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}"); + waitStackResolved(5, PhaseStep.PRECOMBAT_MAIN, playerA); + + playLand(5, PhaseStep.PRECOMBAT_MAIN, playerA, "Plains"); + castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, LION); + + setStrictChooseMode(true); + setStopAt(5, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, LION, 1); + assertPermanentCount(playerA, "Plains", 2); + } + +}