diff --git a/Mage.Sets/src/mage/cards/s/StarvingRevenant.java b/Mage.Sets/src/mage/cards/s/StarvingRevenant.java
new file mode 100644
index 00000000000..8d0a80c749d
--- /dev/null
+++ b/Mage.Sets/src/mage/cards/s/StarvingRevenant.java
@@ -0,0 +1,93 @@
+package mage.cards.s;
+
+import mage.MageInt;
+import mage.abilities.Ability;
+import mage.abilities.common.DrawCardControllerTriggeredAbility;
+import mage.abilities.common.EntersBattlefieldTriggeredAbility;
+import mage.abilities.condition.common.DescendCondition;
+import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
+import mage.abilities.effects.OneShotEffect;
+import mage.abilities.effects.common.GainLifeEffect;
+import mage.abilities.effects.common.LoseLifeTargetEffect;
+import mage.cards.CardImpl;
+import mage.cards.CardSetInfo;
+import mage.constants.AbilityWord;
+import mage.constants.CardType;
+import mage.constants.Outcome;
+import mage.constants.SubType;
+import mage.game.Game;
+import mage.players.Player;
+import mage.target.common.TargetOpponent;
+
+import java.util.UUID;
+
+/**
+ * @author Susucr
+ */
+public final class StarvingRevenant extends CardImpl {
+
+ public StarvingRevenant(UUID ownerId, CardSetInfo setInfo) {
+ super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}");
+
+ this.subtype.add(SubType.SPIRIT);
+ this.subtype.add(SubType.HORROR);
+ this.power = new MageInt(4);
+ this.toughness = new MageInt(4);
+
+ // When Starving Revenant enters the battlefield, surveil 2. Then for each card you put on top of your library, you draw a card and you lose 3 life.
+ this.addAbility(new EntersBattlefieldTriggeredAbility(new StarvingRevenantEffect()));
+
+ // Descend 8 -- Whenever you draw a card, if there are eight or more permanent cards in your graveyard, target opponent loses 1 life and you gain 1 life.
+ Ability ability = new ConditionalInterveningIfTriggeredAbility(
+ new DrawCardControllerTriggeredAbility(new LoseLifeTargetEffect(1), false),
+ DescendCondition.EIGHT, "Whenever you draw a card, "
+ + "if there are eight or more permanent cards in your graveyard, "
+ + "target opponent loses 1 life and you gain 1 life."
+ );
+ ability.addEffect(new GainLifeEffect(1));
+ ability.addTarget(new TargetOpponent());
+ this.addAbility(ability.addHint(DescendCondition.getHint()).setAbilityWord(AbilityWord.DESCEND_8));
+ }
+
+ private StarvingRevenant(final StarvingRevenant card) {
+ super(card);
+ }
+
+ @Override
+ public StarvingRevenant copy() {
+ return new StarvingRevenant(this);
+ }
+}
+
+class StarvingRevenantEffect extends OneShotEffect {
+
+ StarvingRevenantEffect() {
+ super(Outcome.Benefit);
+ staticText = "surveil 2. Then for each card you put on top of your library, you draw a card and you lose 3 life";
+ }
+
+ private StarvingRevenantEffect(final StarvingRevenantEffect effect) {
+ super(effect);
+ }
+
+ @Override
+ public StarvingRevenantEffect copy() {
+ return new StarvingRevenantEffect(this);
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ Player controller = game.getPlayer(source.getControllerId());
+ if (controller == null) {
+ return false;
+ }
+
+ int amountOnTop = controller.doSurveil(2, source, game).getNumberPutOnTop();
+ for (int i = 0; i < amountOnTop; ++i) {
+ controller.drawCards(1, source, game);
+ controller.loseLife(3, game, source, false);
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java
index 9677d56a336..8834d1570a4 100644
--- a/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java
+++ b/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalan.java
@@ -162,6 +162,7 @@ public final class TheLostCavernsOfIxalan extends ExpansionSet {
cards.add(new SetCardInfo("Spyglass Siren", 78, Rarity.UNCOMMON, mage.cards.s.SpyglassSiren.class));
cards.add(new SetCardInfo("Squirming Emergence", 241, Rarity.RARE, mage.cards.s.SquirmingEmergence.class));
cards.add(new SetCardInfo("Stalactite Stalker", 122, Rarity.RARE, mage.cards.s.StalactiteStalker.class));
+ cards.add(new SetCardInfo("Starving Revenant", 123, Rarity.RARE, mage.cards.s.StarvingRevenant.class));
cards.add(new SetCardInfo("Staunch Crewmate", 79, Rarity.UNCOMMON, mage.cards.s.StaunchCrewmate.class));
cards.add(new SetCardInfo("Stinging Cave Crawler", 124, Rarity.UNCOMMON, mage.cards.s.StingingCaveCrawler.class));
cards.add(new SetCardInfo("Sunbird Effigy", 262, Rarity.UNCOMMON, mage.cards.s.SunbirdEffigy.class));
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/StarvingRevenantTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/StarvingRevenantTest.java
new file mode 100644
index 00000000000..0de2b640260
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lci/StarvingRevenantTest.java
@@ -0,0 +1,87 @@
+package org.mage.test.cards.single.lci;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import org.junit.Test;
+import org.mage.test.player.TestPlayer;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ * @author Susucr
+ */
+public class StarvingRevenantTest extends CardTestPlayerBase {
+
+ /**
+ * {@link mage.cards.s.StarvingRevenant}
+ * Starving Revenant {2}{B}{B}
+ * Creature — Spirit Horror
+ * When Starving Revenant enters the battlefield, surveil 2. Then for each card you put on top of your library, you draw a card and you lose 3 life.
+ * Descend 8 — Whenever you draw a card, if there are eight or more permanent cards in your graveyard, target opponent loses 1 life and you gain 1 life.
+ * 4/4
+ */
+ private static final String revenant = "Starving Revenant";
+
+ @Test
+ public void surveil_both_graveyard() {
+ setStrictChooseMode(true);
+ skipInitShuffling();
+
+ addCard(Zone.HAND, playerA, revenant);
+ addCard(Zone.LIBRARY, playerA, "Plains");
+ addCard(Zone.LIBRARY, playerA, "Island");
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, revenant);
+ addTarget(playerA, "Plains^Island");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertLife(playerA, 20);
+ assertGraveyardCount(playerA, 2);
+ assertHandCount(playerA, 0);
+ }
+
+ @Test
+ public void surveil_one_on_top() {
+ setStrictChooseMode(true);
+ skipInitShuffling();
+
+ addCard(Zone.HAND, playerA, revenant);
+ addCard(Zone.LIBRARY, playerA, "Plains");
+ addCard(Zone.LIBRARY, playerA, "Island");
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, revenant);
+ addTarget(playerA, "Plains");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertLife(playerA, 20 - 3);
+ assertGraveyardCount(playerA, 1);
+ assertHandCount(playerA, 1);
+ }
+
+ @Test
+ public void surveil_both_on_top() {
+ setStrictChooseMode(true);
+ skipInitShuffling();
+
+ addCard(Zone.HAND, playerA, revenant);
+ addCard(Zone.LIBRARY, playerA, "Plains");
+ addCard(Zone.LIBRARY, playerA, "Island");
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, revenant);
+ addTarget(playerA, TestPlayer.TARGET_SKIP);
+ setChoice(playerA, "Plains"); // Plains put on top first
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertLife(playerA, 20 - 3 * 2);
+ assertGraveyardCount(playerA, 0);
+ assertHandCount(playerA, 2);
+ }
+}
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 7485abfb40f..2f1f11b54e7 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
@@ -1145,7 +1145,7 @@ public class TestPlayer implements Player {
Assert.fail(action.getActionName() + " - can't find permanent to check: " + cardName);
return null;
}
-
+
private void printStart(Game game, String name) {
System.out.println("\n" + game.toString());
System.out.println(name + ":");
@@ -2867,7 +2867,7 @@ public class TestPlayer implements Player {
@Override
public List getMultiAmountWithIndividualConstraints(Outcome outcome, List messages,
- int min, int max, MultiAmountType type, Game game) {
+ int min, int max, MultiAmountType type, Game game) {
assertAliasSupportInChoices(false);
int needCount = messages.size();
@@ -3171,12 +3171,12 @@ public class TestPlayer implements Player {
}
@Override
- public Map>> getCastSourceIdManaCosts() {
+ public Map>> getCastSourceIdManaCosts() {
return computerPlayer.getCastSourceIdManaCosts();
}
@Override
- public Map>> getCastSourceIdCosts() {
+ public Map>> getCastSourceIdCosts() {
return computerPlayer.getCastSourceIdCosts();
}
@@ -4372,10 +4372,8 @@ public class TestPlayer implements Player {
}
@Override
- public boolean surveil(int value, Ability source,
- Game game
- ) {
- return computerPlayer.surveil(value, source, game);
+ public SurveilResult doSurveil(int value, Ability source, Game game) {
+ return computerPlayer.doSurveil(value, source, game);
}
@Override
diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
index dca7330c7da..fe0d5232ae0 100644
--- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
+++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
@@ -986,7 +986,7 @@ public class PlayerStub implements Player {
@Override
public List getMultiAmountWithIndividualConstraints(Outcome outcome, List messages,
- int min, int max, MultiAmountType type, Game game) {
+ int min, int max, MultiAmountType type, Game game) {
return null;
}
@@ -1281,7 +1281,7 @@ public class PlayerStub implements Player {
}
@Override
- public Map>> getCastSourceIdCosts() {
+ public Map>> getCastSourceIdCosts() {
return null;
}
@@ -1346,8 +1346,8 @@ public class PlayerStub implements Player {
}
@Override
- public boolean surveil(int value, Ability source, Game game) {
- return false;
+ public SurveilResult doSurveil(int value, Ability source, Game game) {
+ return SurveilResult.noSurveil();
}
@Override
diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java
index 2965b1da565..363a44ab517 100644
--- a/Mage/src/main/java/mage/players/Player.java
+++ b/Mage/src/main/java/mage/players/Player.java
@@ -1108,7 +1108,48 @@ public interface Player extends MageItem, Copyable {
boolean scry(int value, Ability source, Game game);
- boolean surveil(int value, Ability source, Game game);
+ /**
+ * result of the doSurveil action.
+ * Sometimes more info is needed for the caller after the surveil is done.
+ */
+ class SurveilResult {
+ private final boolean surveilled;
+ private final int numberInGraveyard; // how many cards were put into the graveyard
+ private final int numberOnTop; // how many cards were put into the graveyard
+
+ private SurveilResult(boolean surveilled, int inGrave, int onTop) {
+ this.surveilled = surveilled;
+ this.numberInGraveyard = inGrave;
+ this.numberOnTop = onTop;
+ }
+
+ public static SurveilResult noSurveil() {
+ return new SurveilResult(false, 0, 0);
+ }
+
+ public static SurveilResult surveil(int inGrave, int onTop) {
+ return new SurveilResult(true, inGrave, onTop);
+ }
+
+ public boolean hasSurveilled() {
+ return this.surveilled;
+ }
+
+ public int getNumberPutInGraveyard() {
+ return this.numberInGraveyard;
+ }
+
+ public int getNumberPutOnTop() {
+ return this.numberOnTop;
+ }
+ }
+
+ SurveilResult doSurveil(int value, Ability source, Game game);
+
+ default boolean surveil(int value, Ability source, Game game) {
+ SurveilResult result = doSurveil(value, source, game);
+ return result.hasSurveilled();
+ }
/**
* Only used for test player for pre-setting targets
diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java
index c110093d8b8..9854d1653ec 100644
--- a/Mage/src/main/java/mage/players/PlayerImpl.java
+++ b/Mage/src/main/java/mage/players/PlayerImpl.java
@@ -287,13 +287,13 @@ public abstract class PlayerImpl implements Player, Serializable {
}
for (Entry>> entry : player.getCastSourceIdManaCosts().entrySet()) {
this.castSourceIdManaCosts.put(entry.getKey(), new HashMap<>());
- for(Entry> subEntry : entry.getValue().entrySet()) {
+ for (Entry> subEntry : entry.getValue().entrySet()) {
this.castSourceIdManaCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
}
}
for (Entry>> entry : player.getCastSourceIdCosts().entrySet()) {
this.castSourceIdCosts.put(entry.getKey(), new HashMap<>());
- for(Entry> subEntry : entry.getValue().entrySet()) {
+ for (Entry> subEntry : entry.getValue().entrySet()) {
this.castSourceIdCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
}
}
@@ -381,13 +381,13 @@ public abstract class PlayerImpl implements Player, Serializable {
}
for (Entry>> entry : player.getCastSourceIdManaCosts().entrySet()) {
this.castSourceIdManaCosts.put(entry.getKey(), new HashMap<>());
- for(Entry> subEntry : entry.getValue().entrySet()) {
+ for (Entry> subEntry : entry.getValue().entrySet()) {
this.castSourceIdManaCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
}
}
for (Entry>> entry : player.getCastSourceIdCosts().entrySet()) {
this.castSourceIdCosts.put(entry.getKey(), new HashMap<>());
- for(Entry> subEntry : entry.getValue().entrySet()) {
+ for (Entry> subEntry : entry.getValue().entrySet()) {
this.castSourceIdCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
}
}
@@ -3551,7 +3551,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
// ALTERNATIVE COST FROM dynamic effects
- for(MageIdentifier identifier : getCastSourceIdWithAlternateMana().getOrDefault(copy.getSourceId(), new HashSet<>())) {
+ for (MageIdentifier identifier : getCastSourceIdWithAlternateMana().getOrDefault(copy.getSourceId(), new HashSet<>())) {
ManaCosts alternateCosts = getCastSourceIdManaCosts().get(copy.getSourceId()).get(identifier);
Costs costs = getCastSourceIdCosts().get(copy.getSourceId()).get(identifier);
@@ -5134,14 +5134,15 @@ public abstract class PlayerImpl implements Player, Serializable {
}
@Override
- public boolean surveil(int value, Ability source, Game game) {
+ public SurveilResult doSurveil(int value, Ability source, Game game) {
GameEvent event = new GameEvent(GameEvent.EventType.SURVEIL, getId(), source, getId(), value, true);
if (game.replaceEvent(event)) {
- return false;
+ return SurveilResult.noSurveil();
}
game.informPlayers(getLogName() + " surveils " + event.getAmount() + CardUtil.getSourceLogName(game, source));
Cards cards = new CardsImpl();
cards.addAllCards(getLibrary().getTopCards(game, event.getAmount()));
+ int totalCount = cards.size();
if (!cards.isEmpty()) {
TargetCard target = new TargetCard(0, cards.size(), Zone.LIBRARY,
new FilterCard("card" + (cards.size() == 1 ? "" : "s")
@@ -5152,7 +5153,7 @@ public abstract class PlayerImpl implements Player, Serializable {
putCardsOnTopOfLibrary(cards, game, source, true);
}
game.fireEvent(new GameEvent(GameEvent.EventType.SURVEILED, getId(), source, getId(), event.getAmount(), true));
- return true;
+ return SurveilResult.surveil(totalCount - cards.size(), cards.size());
}
@Override