mirror of
https://github.com/magefree/mage.git
synced 2025-12-21 19:11:59 -08:00
fix River Song (#12727)
This commit is contained in:
parent
317f536dc9
commit
9fe5d6bd1b
6 changed files with 100 additions and 39 deletions
|
|
@ -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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue