fix River Song (#12727)

This commit is contained in:
xenohedron 2024-08-24 18:36:42 -04:00 committed by GitHub
parent 317f536dc9
commit 9fe5d6bd1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 100 additions and 39 deletions

View file

@ -6,8 +6,8 @@ import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.SourcePermanentPowerCount; import mage.abilities.dynamicvalue.common.SourcePermanentPowerCount;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
@ -15,7 +15,6 @@ import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.*;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.game.Game; import mage.game.Game;
import mage.game.events.DrawCardEvent;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.players.Player; import mage.players.Player;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
@ -38,7 +37,7 @@ public final class RiverSong extends CardImpl {
this.toughness = new MageInt(2); this.toughness = new MageInt(2);
// Meet in Reverse -- You draw cards from the bottom of your library rather than the top. // 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")); .withFlavorWord("Meet in Reverse"));
// Spoilers -- Whenever an opponent scries, surveils, or searches their library, put a +1/+1 counter on River Song. // 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() { RiverSongDrawFromBottomEffect() {
super(Duration.WhileOnBattlefield, Outcome.Neutral); super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit);
staticText = "You draw cards from the bottom of your library rather than the top"; 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); super(effect);
} }
@Override @Override
public RiverSongDrawFromBottomReplacementEffect copy() { public RiverSongDrawFromBottomEffect copy() {
return new RiverSongDrawFromBottomReplacementEffect(this); return new RiverSongDrawFromBottomEffect(this);
} }
@Override @Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) { public boolean apply(Game game, Ability source) {
((DrawCardEvent) event).setFromBottom(true); Player player = game.getPlayer(source.getControllerId());
return false; if (player == null) {
} return false;
}
@Override player.setDrawsFromBottom(true);
public boolean checksEventType(GameEvent event, Game game) { return true;
return event.getType() == GameEvent.EventType.DRAW_CARD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return source.getControllerId().equals(event.getPlayerId());
} }
} }

View file

@ -480,4 +480,53 @@ public class DrawEffectsTest extends CardTestPlayerBase {
assertLibraryCount(playerA, "Shock", 1); 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);
}
} }

View file

@ -3847,11 +3847,6 @@ public class TestPlayer implements Player {
return computerPlayer.canPlayCardsFromGraveyard(); return computerPlayer.canPlayCardsFromGraveyard();
} }
@Override
public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) {
computerPlayer.setDrawsOnOpponentsTurn(drawsOnOpponentsTurn);
}
@Override @Override
public boolean canPlotFromTopOfLibrary() { public boolean canPlotFromTopOfLibrary() {
return computerPlayer.canPlotFromTopOfLibrary(); return computerPlayer.canPlotFromTopOfLibrary();
@ -3862,6 +3857,21 @@ public class TestPlayer implements Player {
computerPlayer.setPlotFromTopOfLibrary(canPlotFromTopOfLibrary); 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 @Override
public boolean isDrawsOnOpponentsTurn() { public boolean isDrawsOnOpponentsTurn() {
return computerPlayer.isDrawsOnOpponentsTurn(); return computerPlayer.isDrawsOnOpponentsTurn();

View file

@ -9,8 +9,6 @@ import java.util.UUID;
*/ */
public class DrawCardEvent extends GameEvent { 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" private int cardsDrawn = 0; // for replacement effects to keep track for "cards drawn this way"
public DrawCardEvent(UUID playerId, Ability source, GameEvent originalDrawEvent) { 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) { public void incrementCardsDrawn(int cardsDrawn) {
this.cardsDrawn += cardsDrawn; this.cardsDrawn += cardsDrawn;
} }

View file

@ -204,6 +204,10 @@ public interface Player extends MageItem, Copyable<Player> {
boolean canPlotFromTopOfLibrary(); boolean canPlotFromTopOfLibrary();
void setDrawsFromBottom(boolean drawsFromBottom);
boolean isDrawsFromBottom();
void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn); void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn);
boolean isDrawsOnOpponentsTurn(); boolean isDrawsOnOpponentsTurn();

View file

@ -154,6 +154,7 @@ public abstract class PlayerImpl implements Player, Serializable {
protected boolean loseByZeroOrLessLife = true; protected boolean loseByZeroOrLessLife = true;
protected boolean canPlayCardsFromGraveyard = true; protected boolean canPlayCardsFromGraveyard = true;
protected boolean canPlotFromTopOfLibrary = false; protected boolean canPlotFromTopOfLibrary = false;
protected boolean drawsFromBottom = false;
protected boolean drawsOnOpponentsTurn = false; protected boolean drawsOnOpponentsTurn = false;
protected FilterPermanent sacrificeCostFilter; protected FilterPermanent sacrificeCostFilter;
@ -253,6 +254,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.loseByZeroOrLessLife = player.loseByZeroOrLessLife; this.loseByZeroOrLessLife = player.loseByZeroOrLessLife;
this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard; this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard;
this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary; this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary;
this.drawsFromBottom = player.drawsFromBottom;
this.drawsOnOpponentsTurn = player.drawsOnOpponentsTurn; this.drawsOnOpponentsTurn = player.drawsOnOpponentsTurn;
this.attachments.addAll(player.attachments); this.attachments.addAll(player.attachments);
@ -367,6 +369,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife(); this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife();
this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard(); this.canPlayCardsFromGraveyard = player.canPlayCardsFromGraveyard();
this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary(); this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary();
this.drawsFromBottom = player.isDrawsFromBottom();
this.drawsOnOpponentsTurn = player.isDrawsOnOpponentsTurn(); this.drawsOnOpponentsTurn = player.isDrawsOnOpponentsTurn();
this.alternativeSourceCosts = CardUtil.deepCopyObject(player.getAlternativeSourceCosts()); this.alternativeSourceCosts = CardUtil.deepCopyObject(player.getAlternativeSourceCosts());
@ -481,6 +484,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.loseByZeroOrLessLife = true; this.loseByZeroOrLessLife = true;
this.canPlayCardsFromGraveyard = true; this.canPlayCardsFromGraveyard = true;
this.canPlotFromTopOfLibrary = false; this.canPlotFromTopOfLibrary = false;
this.drawsFromBottom = false;
this.drawsOnOpponentsTurn = false; this.drawsOnOpponentsTurn = false;
this.sacrificeCostFilter = null; this.sacrificeCostFilter = null;
@ -524,6 +528,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.loseByZeroOrLessLife = true; this.loseByZeroOrLessLife = true;
this.canPlayCardsFromGraveyard = false; this.canPlayCardsFromGraveyard = false;
this.canPlotFromTopOfLibrary = false; this.canPlotFromTopOfLibrary = false;
this.drawsFromBottom = false;
this.drawsOnOpponentsTurn = false; this.drawsOnOpponentsTurn = false;
this.topCardRevealed = false; this.topCardRevealed = false;
this.alternativeSourceCosts.clear(); this.alternativeSourceCosts.clear();
@ -768,7 +773,7 @@ public abstract class PlayerImpl implements Player, Serializable {
numDrawn += drawCardEvent.getCardsDrawn(); numDrawn += drawCardEvent.getCardsDrawn();
continue; continue;
} }
Card card = drawCardEvent.isFromBottom() ? getLibrary().drawFromBottom(game) : getLibrary().drawFromTop(game); Card card = isDrawsFromBottom() ? getLibrary().drawFromBottom(game) : getLibrary().drawFromTop(game);
if (card != null) { if (card != null) {
card.moveToZone(Zone.HAND, source, game, false); // if you want to use event.getSourceId() here then thinks x10 times card.moveToZone(Zone.HAND, source, game, false); // if you want to use event.getSourceId() here then thinks x10 times
if (isTopCardRevealed()) { if (isTopCardRevealed()) {
@ -4661,6 +4666,16 @@ public abstract class PlayerImpl implements Player, Serializable {
this.canPlotFromTopOfLibrary = canPlotFromTopOfLibrary; this.canPlotFromTopOfLibrary = canPlotFromTopOfLibrary;
} }
@Override
public void setDrawsFromBottom(boolean drawsFromBottom) {
this.drawsFromBottom = drawsFromBottom;
}
@Override
public boolean isDrawsFromBottom() {
return drawsFromBottom;
}
@Override @Override
public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) { public void setDrawsOnOpponentsTurn(boolean drawsOnOpponentsTurn) {
this.drawsOnOpponentsTurn = drawsOnOpponentsTurn; this.drawsOnOpponentsTurn = drawsOnOpponentsTurn;