From 9fe5d6bd1b33e50af24f553d7b9ddfa622a5d26a Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sat, 24 Aug 2024 18:36:42 -0400 Subject: [PATCH] fix River Song (#12727) --- Mage.Sets/src/mage/cards/r/RiverSong.java | 39 ++++++--------- .../cards/replacement/DrawEffectsTest.java | 49 +++++++++++++++++++ .../java/org/mage/test/player/TestPlayer.java | 20 ++++++-- .../java/mage/game/events/DrawCardEvent.java | 10 ---- Mage/src/main/java/mage/players/Player.java | 4 ++ .../main/java/mage/players/PlayerImpl.java | 17 ++++++- 6 files changed, 100 insertions(+), 39 deletions(-) diff --git a/Mage.Sets/src/mage/cards/r/RiverSong.java b/Mage.Sets/src/mage/cards/r/RiverSong.java index 6829d15dd41..a8d43373dcf 100644 --- a/Mage.Sets/src/mage/cards/r/RiverSong.java +++ b/Mage.Sets/src/mage/cards/r/RiverSong.java @@ -6,8 +6,8 @@ import mage.abilities.TriggeredAbility; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.SourcePermanentPowerCount; +import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.Effect; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.cards.CardImpl; @@ -15,7 +15,6 @@ import mage.cards.CardSetInfo; import mage.constants.*; import mage.counters.CounterType; import mage.game.Game; -import mage.game.events.DrawCardEvent; import mage.game.events.GameEvent; import mage.players.Player; import mage.target.targetpointer.FixedTarget; @@ -38,7 +37,7 @@ public final class RiverSong extends CardImpl { this.toughness = new MageInt(2); // Meet in Reverse -- You draw cards from the bottom of your library rather than the top. - this.addAbility(new SimpleStaticAbility(new RiverSongDrawFromBottomReplacementEffect()) + this.addAbility(new SimpleStaticAbility(new RiverSongDrawFromBottomEffect()) .withFlavorWord("Meet in Reverse")); // Spoilers -- Whenever an opponent scries, surveils, or searches their library, put a +1/+1 counter on River Song. @@ -59,36 +58,30 @@ public final class RiverSong extends CardImpl { } } -class RiverSongDrawFromBottomReplacementEffect extends ReplacementEffectImpl { +class RiverSongDrawFromBottomEffect extends ContinuousEffectImpl { - RiverSongDrawFromBottomReplacementEffect() { - super(Duration.WhileOnBattlefield, Outcome.Neutral); - staticText = "You draw cards from the bottom of your library rather than the top"; + RiverSongDrawFromBottomEffect() { + super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit); + staticText = "you draw cards from the bottom of your library rather than the top"; } - private RiverSongDrawFromBottomReplacementEffect(final RiverSongDrawFromBottomReplacementEffect effect) { + private RiverSongDrawFromBottomEffect(final RiverSongDrawFromBottomEffect effect) { super(effect); } @Override - public RiverSongDrawFromBottomReplacementEffect copy() { - return new RiverSongDrawFromBottomReplacementEffect(this); + public RiverSongDrawFromBottomEffect copy() { + return new RiverSongDrawFromBottomEffect(this); } @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - ((DrawCardEvent) event).setFromBottom(true); - return false; - } - - @Override - public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DRAW_CARD; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - return source.getControllerId().equals(event.getPlayerId()); + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.setDrawsFromBottom(true); + return true; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DrawEffectsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DrawEffectsTest.java index 7df5061fd1d..e61947d5d26 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DrawEffectsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/DrawEffectsTest.java @@ -480,4 +480,53 @@ public class DrawEffectsTest extends CardTestPlayerBase { assertLibraryCount(playerA, "Shock", 1); } + @Test + public void testRiverSongExtended() { + skipInitShuffling(); + removeAllCardsFromLibrary(playerA); + addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 3); + addCard(Zone.LIBRARY, playerA, "Healing Salve"); // bottom + addCard(Zone.LIBRARY, playerA, "Giant Growth"); + addCard(Zone.LIBRARY, playerA, "Dark Ritual"); + addCard(Zone.LIBRARY, playerA, "Ornithopter"); + addCard(Zone.LIBRARY, playerA, "Shock"); // top + addCard(Zone.BATTLEFIELD, playerA, "Blood Bairn"); // sac outlet + addCard(Zone.HAND, playerA, "River Song"); + // You draw cards from the bottom of your library rather than the top. + + checkHandCardCount("first draw", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Shock", 1); + + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "River Song"); + + checkHandCardCount("second draw", 5, PhaseStep.PRECOMBAT_MAIN, playerA, "Healing Salve", 1); + + checkHandCardCount("third draw", 7, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", 1); + + activateAbility(7, PhaseStep.POSTCOMBAT_MAIN, playerA, "Sacrifice"); + setChoice(playerA, "River Song"); + + checkHandCardCount("fourth draw", 9, PhaseStep.PRECOMBAT_MAIN, playerA, "Ornithopter", 1); + + setStrictChooseMode(true); + setStopAt(9, PhaseStep.BEGIN_COMBAT); + execute(); + + assertHandCount(playerA, 4); + assertLibraryCount(playerA, "Dark Ritual", 1); + assertGraveyardCount(playerA, "River Song", 1); + } + + @Test + public void testRiverSongLaboratoryManiac() { + removeAllCardsFromLibrary(playerA); + addCard(Zone.BATTLEFIELD, playerA, "River Song"); + addCard(Zone.BATTLEFIELD, playerA, "Laboratory Maniac"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertWonTheGame(playerA); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 7983c3b14d3..cf90889e0ea 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -3847,11 +3847,6 @@ public class TestPlayer implements Player { return computerPlayer.canPlayCardsFromGraveyard(); } - @Override - public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { - computerPlayer.setDrawsOnOpponentsTurn(drawsOnOpponentsTurn); - } - @Override public boolean canPlotFromTopOfLibrary() { return computerPlayer.canPlotFromTopOfLibrary(); @@ -3862,6 +3857,21 @@ public class TestPlayer implements Player { computerPlayer.setPlotFromTopOfLibrary(canPlotFromTopOfLibrary); } + @Override + public void setDrawsFromBottom(boolean drawsFromBottom) { + computerPlayer.setDrawsFromBottom(drawsFromBottom); + } + + @Override + public boolean isDrawsFromBottom() { + return computerPlayer.isDrawsFromBottom(); + } + + @Override + public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { + computerPlayer.setDrawsOnOpponentsTurn(drawsOnOpponentsTurn); + } + @Override public boolean isDrawsOnOpponentsTurn() { return computerPlayer.isDrawsOnOpponentsTurn(); diff --git a/Mage/src/main/java/mage/game/events/DrawCardEvent.java b/Mage/src/main/java/mage/game/events/DrawCardEvent.java index f5c62db4621..e77ee187299 100644 --- a/Mage/src/main/java/mage/game/events/DrawCardEvent.java +++ b/Mage/src/main/java/mage/game/events/DrawCardEvent.java @@ -9,8 +9,6 @@ import java.util.UUID; */ public class DrawCardEvent extends GameEvent { - private boolean fromBottom = false; // for replacement effects that draw from bottom of library instead - private int cardsDrawn = 0; // for replacement effects to keep track for "cards drawn this way" public DrawCardEvent(UUID playerId, Ability source, GameEvent originalDrawEvent) { @@ -27,14 +25,6 @@ public class DrawCardEvent extends GameEvent { } } - public void setFromBottom(boolean fromBottom) { - this.fromBottom = fromBottom; - } - - public boolean isFromBottom() { - return fromBottom; - } - public void incrementCardsDrawn(int cardsDrawn) { this.cardsDrawn += cardsDrawn; } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index a555c276c70..d31f51d849d 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -204,6 +204,10 @@ public interface Player extends MageItem, Copyable { boolean canPlotFromTopOfLibrary(); + void setDrawsFromBottom(boolean drawsFromBottom); + + boolean isDrawsFromBottom(); + void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn); boolean isDrawsOnOpponentsTurn(); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 03e45945f97..c16aeb3f188 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -154,6 +154,7 @@ public abstract class PlayerImpl implements Player, Serializable { protected boolean loseByZeroOrLessLife = true; protected boolean canPlayCardsFromGraveyard = true; protected boolean canPlotFromTopOfLibrary = false; + protected boolean drawsFromBottom = false; protected boolean drawsOnOpponentsTurn = false; protected FilterPermanent sacrificeCostFilter; @@ -253,6 +254,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.loseByZeroOrLessLife = player.loseByZeroOrLessLife; this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard; this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary; + this.drawsFromBottom = player.drawsFromBottom; this.drawsOnOpponentsTurn = player.drawsOnOpponentsTurn; this.attachments.addAll(player.attachments); @@ -367,6 +369,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife(); this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard(); this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary(); + this.drawsFromBottom = player.isDrawsFromBottom(); this.drawsOnOpponentsTurn = player.isDrawsOnOpponentsTurn(); this.alternativeSourceCosts = CardUtil.deepCopyObject(player.getAlternativeSourceCosts()); @@ -481,6 +484,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.loseByZeroOrLessLife = true; this.canPlayCardsFromGraveyard = true; this.canPlotFromTopOfLibrary = false; + this.drawsFromBottom = false; this.drawsOnOpponentsTurn = false; this.sacrificeCostFilter = null; @@ -524,6 +528,7 @@ public abstract class PlayerImpl implements Player, Serializable { this.loseByZeroOrLessLife = true; this.canPlayCardsFromGraveyard = false; this.canPlotFromTopOfLibrary = false; + this.drawsFromBottom = false; this.drawsOnOpponentsTurn = false; this.topCardRevealed = false; this.alternativeSourceCosts.clear(); @@ -768,7 +773,7 @@ public abstract class PlayerImpl implements Player, Serializable { numDrawn += drawCardEvent.getCardsDrawn(); continue; } - Card card = drawCardEvent.isFromBottom() ? getLibrary().drawFromBottom(game) : getLibrary().drawFromTop(game); + Card card = isDrawsFromBottom() ? getLibrary().drawFromBottom(game) : getLibrary().drawFromTop(game); if (card != null) { card.moveToZone(Zone.HAND, source, game, false); // if you want to use event.getSourceId() here then thinks x10 times if (isTopCardRevealed()) { @@ -4661,6 +4666,16 @@ public abstract class PlayerImpl implements Player, Serializable { this.canPlotFromTopOfLibrary = canPlotFromTopOfLibrary; } + @Override + public void setDrawsFromBottom(boolean drawsFromBottom) { + this.drawsFromBottom = drawsFromBottom; + } + + @Override + public boolean isDrawsFromBottom() { + return drawsFromBottom; + } + @Override public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { this.drawsOnOpponentsTurn = drawsOnOpponentsTurn;