mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
[LCI] Implement Starving Revenant (#11378)
This commit is contained in:
parent
3c837e9dff
commit
bab07a421d
7 changed files with 242 additions and 21 deletions
93
Mage.Sets/src/mage/cards/s/StarvingRevenant.java
Normal file
93
Mage.Sets/src/mage/cards/s/StarvingRevenant.java
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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} <br>
|
||||
* Starving Revenant {2}{B}{B} <br>
|
||||
* Creature — Spirit Horror <br>
|
||||
* 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. <br>
|
||||
* 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. <br>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Integer> getMultiAmountWithIndividualConstraints(Outcome outcome, List<MultiAmountMessage> 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<UUID, Map<MageIdentifier,ManaCosts<ManaCost>>> getCastSourceIdManaCosts() {
|
||||
public Map<UUID, Map<MageIdentifier, ManaCosts<ManaCost>>> getCastSourceIdManaCosts() {
|
||||
return computerPlayer.getCastSourceIdManaCosts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, Map<MageIdentifier,Costs<Cost>>> getCastSourceIdCosts() {
|
||||
public Map<UUID, Map<MageIdentifier, Costs<Cost>>> 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
|
||||
|
|
|
|||
|
|
@ -986,7 +986,7 @@ public class PlayerStub implements Player {
|
|||
|
||||
@Override
|
||||
public List<Integer> getMultiAmountWithIndividualConstraints(Outcome outcome, List<MultiAmountMessage> 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<UUID, Map<MageIdentifier,Costs<Cost>>> getCastSourceIdCosts() {
|
||||
public Map<UUID, Map<MageIdentifier, Costs<Cost>>> 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
|
||||
|
|
|
|||
|
|
@ -1108,7 +1108,48 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -287,13 +287,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
for (Entry<UUID, Map<MageIdentifier, ManaCosts<ManaCost>>> entry : player.getCastSourceIdManaCosts().entrySet()) {
|
||||
this.castSourceIdManaCosts.put(entry.getKey(), new HashMap<>());
|
||||
for(Entry<MageIdentifier, ManaCosts<ManaCost>> subEntry : entry.getValue().entrySet()) {
|
||||
for (Entry<MageIdentifier, ManaCosts<ManaCost>> subEntry : entry.getValue().entrySet()) {
|
||||
this.castSourceIdManaCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
|
||||
}
|
||||
}
|
||||
for (Entry<UUID, Map<MageIdentifier, Costs<Cost>>> entry : player.getCastSourceIdCosts().entrySet()) {
|
||||
this.castSourceIdCosts.put(entry.getKey(), new HashMap<>());
|
||||
for(Entry<MageIdentifier, Costs<Cost>> subEntry : entry.getValue().entrySet()) {
|
||||
for (Entry<MageIdentifier, Costs<Cost>> 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<UUID, Map<MageIdentifier, ManaCosts<ManaCost>>> entry : player.getCastSourceIdManaCosts().entrySet()) {
|
||||
this.castSourceIdManaCosts.put(entry.getKey(), new HashMap<>());
|
||||
for(Entry<MageIdentifier, ManaCosts<ManaCost>> subEntry : entry.getValue().entrySet()) {
|
||||
for (Entry<MageIdentifier, ManaCosts<ManaCost>> subEntry : entry.getValue().entrySet()) {
|
||||
this.castSourceIdManaCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
|
||||
}
|
||||
}
|
||||
for (Entry<UUID, Map<MageIdentifier, Costs<Cost>>> entry : player.getCastSourceIdCosts().entrySet()) {
|
||||
this.castSourceIdCosts.put(entry.getKey(), new HashMap<>());
|
||||
for(Entry<MageIdentifier, Costs<Cost>> subEntry : entry.getValue().entrySet()) {
|
||||
for (Entry<MageIdentifier, Costs<Cost>> 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<Cost> 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue