Skip to content

Commit

Permalink
Rework drawing cards and associated replacement effects; implement [W…
Browse files Browse the repository at this point in the history
…HO] River Song (#12700)

* remove unused scoring system code

* add test for Alms Collector replacement effect

* flatten draw cards into single method in PlayerImpl

* remove outdated MageAction framework

* clarify game event for drawing two or more cards

* clarify methods for getting cards from library

* implement [WHO] River Song

* fix error

* adjust library methods

* add lots of test cases for draw replacement effects

* fix #12616

* track cards drawn this way through multi draw replacement as well

* add test for River Song

* remove redundant comment
  • Loading branch information
xenohedron authored Aug 24, 2024
1 parent 34ae226 commit 9fcbfde
Show file tree
Hide file tree
Showing 22 changed files with 634 additions and 334 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ protected Game createMCTSGame(Game game) {
newPlayer.getHand().clear();
newPlayer.getLibrary().shuffle();
for (int i = 0; i < handSize; i++) {
Card card = newPlayer.getLibrary().removeFromTop(mcts);
Card card = newPlayer.getLibrary().drawFromTop(mcts);
card.setZone(Zone.HAND, mcts);
newPlayer.getHand().add(card);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.PlayLandAbility;
import mage.abilities.common.PassAbility;
import mage.cards.Card;
Expand Down Expand Up @@ -270,7 +269,7 @@ protected void randomizePlayers(Game game, UUID playerId) {
player.getHand().clear();
player.getLibrary().shuffle();
for (int i = 0; i < handSize; i++) {
Card card = player.getLibrary().removeFromTop(game);
Card card = player.getLibrary().drawFromTop(game);
card.setZone(Zone.HAND, game);
player.getHand().add(card);
}
Expand Down
2 changes: 1 addition & 1 deletion Mage.Sets/src/mage/cards/a/AlmsCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public boolean replaceEvent(GameEvent event, Ability source, Game game) {

@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DRAW_CARDS;
return event.getType() == GameEvent.EventType.DRAW_TWO_OR_MORE_CARDS;
}

@Override
Expand Down
6 changes: 1 addition & 5 deletions Mage.Sets/src/mage/cards/b/BloodScrivener.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,7 @@ public boolean checksEventType(GameEvent event, Game game) {
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getPlayerId().equals(source.getControllerId())) {
Player player = game.getPlayer(event.getPlayerId());
if(player != null) {
if (player.getHand().isEmpty()) {
return true;
}
}
return player != null && player.getHand().isEmpty();
}
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion Mage.Sets/src/mage/cards/c/CellarDoor.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public CellarDoorEffect copy() {
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getFirstTarget());
if (player != null && player.getLibrary().hasCards()) {
Card card = player.getLibrary().removeFromBottom(game);
Card card = player.getLibrary().getFromBottom(game);
if (card != null) {
player.moveCards(card, Zone.GRAVEYARD, source, game);
if (card.isCreature(game)) {
Expand Down
2 changes: 1 addition & 1 deletion Mage.Sets/src/mage/cards/l/LidlessGaze.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,4 @@ public boolean apply(Game game, Ability source) {

return true;
}
}
}
134 changes: 134 additions & 0 deletions Mage.Sets/src/mage/cards/r/RiverSong.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package mage.cards.r;

import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.SourcePermanentPowerCount;
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;
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;

import java.util.UUID;

/**
* @author xenohedron
*/
public final class RiverSong extends CardImpl {

public RiverSong(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}{R}");

this.supertype.add(SuperType.LEGENDARY);
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.TIME_LORD);
this.subtype.add(SubType.ROGUE);
this.power = new MageInt(2);
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())
.withFlavorWord("Meet in Reverse"));

// Spoilers -- Whenever an opponent scries, surveils, or searches their library, put a +1/+1 counter on River Song.
// Then River Song deals damage to that player equal to its power.
TriggeredAbility trigger = new RiverSongTriggeredAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance()));
trigger.addEffect(new DamageTargetEffect(new SourcePermanentPowerCount(false))
.setText("Then {this} deals damage to that player equal to its power"));
this.addAbility(trigger.withFlavorWord("Spoilers"));
}

private RiverSong(final RiverSong card) {
super(card);
}

@Override
public RiverSong copy() {
return new RiverSong(this);
}
}

class RiverSongDrawFromBottomReplacementEffect extends ReplacementEffectImpl {

RiverSongDrawFromBottomReplacementEffect() {
super(Duration.WhileOnBattlefield, Outcome.Neutral);
staticText = "You draw cards from the bottom of your library rather than the top";
}

private RiverSongDrawFromBottomReplacementEffect(final RiverSongDrawFromBottomReplacementEffect effect) {
super(effect);
}

@Override
public RiverSongDrawFromBottomReplacementEffect copy() {
return new RiverSongDrawFromBottomReplacementEffect(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());
}
}

class RiverSongTriggeredAbility extends TriggeredAbilityImpl {

RiverSongTriggeredAbility(Effect effect) {
super(Zone.BATTLEFIELD, effect);
setTriggerPhrase("Whenever an opponent scries, surveils, or searches their library, ");
}

private RiverSongTriggeredAbility(final RiverSongTriggeredAbility ability) {
super(ability);
}

@Override
public RiverSongTriggeredAbility copy() {
return new RiverSongTriggeredAbility(this);
}

@Override
public boolean checkEventType(GameEvent event, Game game) {
switch (event.getType()) {
case SCRIED:
case SURVEILED:
case LIBRARY_SEARCHED:
return true;
default:
return false;
}
}

@Override
public boolean checkTrigger(GameEvent event, Game game) {
Player controller = game.getPlayer(getControllerId());
if (controller != null
&& controller.hasOpponent(event.getPlayerId(), game)
&& event.getPlayerId().equals(event.getTargetId())) { // searches own library
getEffects().setTargetPointer(new FixedTarget(event.getPlayerId()));
return true;
}
return false;
}
}
1 change: 1 addition & 0 deletions Mage.Sets/src/mage/sets/DoctorWho.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ private DoctorWho() {
cards.add(new SetCardInfo("Return the Past", 92, Rarity.RARE, mage.cards.r.ReturnThePast.class));
cards.add(new SetCardInfo("Return to Dust", 211, Rarity.UNCOMMON, mage.cards.r.ReturnToDust.class));
cards.add(new SetCardInfo("Reverse the Polarity", 54, Rarity.RARE, mage.cards.r.ReverseThePolarity.class));
cards.add(new SetCardInfo("River Song", 152, Rarity.RARE, mage.cards.r.RiverSong.class));
cards.add(new SetCardInfo("River of Tears", 297, Rarity.RARE, mage.cards.r.RiverOfTears.class));
cards.add(new SetCardInfo("Rockfall Vale", 298, Rarity.RARE, mage.cards.r.RockfallVale.class));
cards.add(new SetCardInfo("Rogue's Passage", 299, Rarity.UNCOMMON, mage.cards.r.RoguesPassage.class));
Expand Down
Loading

0 comments on commit 9fcbfde

Please sign in to comment.