mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
[WOE] Implement Ashiok, Wicked Manipulator (#10909)
* [WOE] Implement Ashiok, Wicket Manipulator * Add Ashiok's abilities * basic pay life replacement tests * many tests later * add warning on token expecting watcher * apply review * rework text generation
This commit is contained in:
parent
fe165f1fd0
commit
2a5dd4103c
15 changed files with 634 additions and 19 deletions
119
Mage.Sets/src/mage/cards/a/AshiokWickedManipulator.java
Normal file
119
Mage.Sets/src/mage/cards/a/AshiokWickedManipulator.java
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.LoyaltyAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.dynamicvalue.common.TotalCardsExiledOwnedManaValue;
|
||||
import mage.abilities.effects.ReplacementEffectImpl;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.effects.common.ExileCardsFromTopOfLibraryTargetEffect;
|
||||
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.token.AshiokWickedManipulatorNightmareToken;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.watchers.common.CardsExiledThisTurnWatcher;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Susucr
|
||||
*/
|
||||
public final class AshiokWickedManipulator extends CardImpl {
|
||||
|
||||
public AshiokWickedManipulator(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{3}{B}{B}");
|
||||
|
||||
this.supertype.add(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.ASHIOK);
|
||||
this.setStartingLoyalty(5);
|
||||
|
||||
// If you would pay life while your library has at least that many cards in it, exile that many cards from the top of your library instead.
|
||||
this.addAbility(new SimpleStaticAbility(new AshiokWickedManipulatorReplacementEffect()));
|
||||
|
||||
// +1: Look at the top two cards of your library. Exile one of them and put the other into your hand.
|
||||
this.addAbility(new LoyaltyAbility(
|
||||
new LookLibraryAndPickControllerEffect(2, 1, PutCards.EXILED, PutCards.HAND),
|
||||
1
|
||||
));
|
||||
|
||||
// -2: Create two 1/1 black Nightmare creature tokens with "At the beginning of combat on your turn, if a card was put into exile this turn, put a +1/+1 counter on this creature."
|
||||
this.addAbility(new LoyaltyAbility(
|
||||
new CreateTokenEffect(new AshiokWickedManipulatorNightmareToken(), 2),
|
||||
-2
|
||||
), new CardsExiledThisTurnWatcher());
|
||||
|
||||
// -7: Target player exiles the top X cards of their library, where X is the total mana value of cards you own in exile.
|
||||
Ability ability = new LoyaltyAbility(
|
||||
new ExileCardsFromTopOfLibraryTargetEffect(TotalCardsExiledOwnedManaValue.instance)
|
||||
.setText("target player exiles the top X cards of their library, "
|
||||
+ "where X is the total mana value of cards you own in exile"),
|
||||
-7
|
||||
);
|
||||
ability.addTarget(new TargetPlayer());
|
||||
ability.addHint(TotalCardsExiledOwnedManaValue.getHint());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
private AshiokWickedManipulator(final AshiokWickedManipulator card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AshiokWickedManipulator copy() {
|
||||
return new AshiokWickedManipulator(this);
|
||||
}
|
||||
}
|
||||
|
||||
class AshiokWickedManipulatorReplacementEffect extends ReplacementEffectImpl {
|
||||
|
||||
AshiokWickedManipulatorReplacementEffect() {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
staticText = "If you would pay life while your library has at least that many cards in it, "
|
||||
+ "exile that many cards from the top of your library instead.";
|
||||
}
|
||||
|
||||
private AshiokWickedManipulatorReplacementEffect(final AshiokWickedManipulatorReplacementEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AshiokWickedManipulatorReplacementEffect copy() {
|
||||
return new AshiokWickedManipulatorReplacementEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<Card> cards = player.getLibrary().getTopCards(game, event.getAmount());
|
||||
player.moveCardsToExile(cards, source, game, false, null, "");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checksEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.PAY_LIFE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
UUID playerId = source.getControllerId();
|
||||
if (!event.getPlayerId().equals(playerId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Player player = game.getPlayer(playerId);
|
||||
return player != null && player.getLibrary().size() >= event.getAmount();
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ public final class OblivionSower extends CardImpl {
|
|||
this.toughness = new MageInt(8);
|
||||
|
||||
// When you cast Oblivion Sower, target opponent exiles the top four cards of their library, then you may put any number of land cards that player owns from exile onto the battlefield under your control.
|
||||
Ability ability = new CastSourceTriggeredAbility(new ExileCardsFromTopOfLibraryTargetEffect(4, "target opponent"), false);
|
||||
Ability ability = new CastSourceTriggeredAbility(new ExileCardsFromTopOfLibraryTargetEffect(4), false);
|
||||
ability.addEffect(new OblivionSowerEffect());
|
||||
ability.addTarget(new TargetOpponent());
|
||||
this.addAbility(ability);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public final class RavenGuildMaster extends CardImpl {
|
|||
this.toughness = new MageInt(1);
|
||||
|
||||
// Whenever Raven Guild Master deals combat damage to a player, that player exiles the top ten cards of their library.
|
||||
this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new ExileCardsFromTopOfLibraryTargetEffect(10, "that player"), false, true));
|
||||
this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new ExileCardsFromTopOfLibraryTargetEffect(10), false, true));
|
||||
|
||||
// Morph {2}{U}{U}
|
||||
this.addAbility(new MorphAbility(new ManaCostsImpl<>("{2}{U}{U}")));
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public final class SireOfStagnation extends CardImpl {
|
|||
|
||||
// Whenever a land enters the battlefield under an opponent's control, that player exiles the top two cards of their library and you draw two cards.
|
||||
Ability ability = new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD,
|
||||
new ExileCardsFromTopOfLibraryTargetEffect(2, "that player"), filter, false, SetTargetPointer.PLAYER, rule, false);
|
||||
new ExileCardsFromTopOfLibraryTargetEffect(2), filter, false, SetTargetPointer.PLAYER, rule, false);
|
||||
ability.addEffect(new DrawCardSourceControllerEffect(2));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public final class ThoughtHarvester extends CardImpl {
|
|||
this.addAbility(FlyingAbility.getInstance());
|
||||
|
||||
// Whenever you cast a colorless spell, target opponent exiles the top card of their library.
|
||||
Ability ability = new SpellCastControllerTriggeredAbility(new ExileCardsFromTopOfLibraryTargetEffect(1, "target opponent"), filter, false);
|
||||
Ability ability = new SpellCastControllerTriggeredAbility(new ExileCardsFromTopOfLibraryTargetEffect(1), filter, false);
|
||||
ability.addTarget(new TargetOpponent());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public final class WildsOfEldraine extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Armory Mice", 3, Rarity.COMMON, mage.cards.a.ArmoryMice.class));
|
||||
cards.add(new SetCardInfo("Ash, Party Crasher", 201, Rarity.UNCOMMON, mage.cards.a.AshPartyCrasher.class));
|
||||
cards.add(new SetCardInfo("Ashiok's Reaper", 79, Rarity.UNCOMMON, mage.cards.a.AshioksReaper.class));
|
||||
cards.add(new SetCardInfo("Ashiok, Wicked Manipulator", 78, Rarity.MYTHIC, mage.cards.a.AshiokWickedManipulator.class));
|
||||
cards.add(new SetCardInfo("Asinine Antics", 42, Rarity.MYTHIC, mage.cards.a.AsinineAntics.class));
|
||||
cards.add(new SetCardInfo("Back for Seconds", 80, Rarity.UNCOMMON, mage.cards.b.BackForSeconds.class));
|
||||
cards.add(new SetCardInfo("Barrow Naughty", 81, Rarity.COMMON, mage.cards.b.BarrowNaughty.class));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,308 @@
|
|||
package org.mage.test.cards.single.woe;
|
||||
|
||||
import mage.cards.Card;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.players.Player;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Susucr
|
||||
*/
|
||||
public class AshiokWickedManipulatorTest extends CardTestPlayerBase {
|
||||
|
||||
/**
|
||||
* Ashiok, Wicked Manipulator
|
||||
* {3}{B}{B}
|
||||
* Legendary Planeswalker — Ashiok
|
||||
* <p>
|
||||
* If you would pay life while your library has at least that many cards in it, exile that many cards from the top of your library instead.
|
||||
* +1: Look at the top two cards of your library. Exile one of them and put the other into your hand.
|
||||
* −2: Create two 1/1 black Nightmare creature tokens with "At the beginning of combat on your turn, if a card was put into exile this turn, put a +1/+1 counter on this creature."
|
||||
* −7: Target player exiles the top X cards of their library, where X is the total mana value of cards you own in exile.
|
||||
* <p>
|
||||
* Loyalty: 5
|
||||
*/
|
||||
private static final String ashiok = "Ashiok, Wicked Manipulator";
|
||||
|
||||
/**
|
||||
* Final Payment
|
||||
* {W}{B}
|
||||
* Instant
|
||||
* <p>
|
||||
* As an additional cost to cast this spell, pay 5 life or sacrifice a creature or enchantment.
|
||||
* <p>
|
||||
* Destroy target creature.
|
||||
*/
|
||||
private static final String finalPayment = "Final Payment";
|
||||
|
||||
/**
|
||||
* Well sometimes you do need a 2/2 vanilla with mana value 2.
|
||||
*/
|
||||
private static final String lion = "Silvercoat Lion";
|
||||
|
||||
/**
|
||||
* Bolas's Citadel
|
||||
* {3}{B}{B}{B}
|
||||
* Legendary Artifact
|
||||
* <p>
|
||||
* You may look at the top card of your library any time.
|
||||
* <p>
|
||||
* You may play lands and cast spells from the top of your library. If you cast a spell this way, pay life equal to its mana value rather than pay its mana cost.
|
||||
* <p>
|
||||
* {T}, Sacrifice ten nonland permanents: Each opponent loses 10 life.
|
||||
*/
|
||||
private static final String citadel = "Bolas's Citadel";
|
||||
|
||||
/**
|
||||
* Lurking Evil
|
||||
* {B}{B}{B}
|
||||
* Enchantment
|
||||
* <p>
|
||||
* Pay half your life, rounded up: Lurking Evil becomes a 4/4 Phyrexian Horror creature with flying.
|
||||
*/
|
||||
private static final String lurking = "Lurking Evil";
|
||||
|
||||
/**
|
||||
* Arrogant Poet
|
||||
* {1}{B}
|
||||
* Creature — Human Warlock
|
||||
* <p>
|
||||
* Whenever Arrogant Poet attacks, you may pay 2 life. If you do, it gains flying until end of turn.
|
||||
*/
|
||||
private static final String poet = "Arrogant Poet";
|
||||
|
||||
@Test
|
||||
public void emptyALibrary() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
skipInitShuffling();
|
||||
removeAllCardsFromLibrary(playerA);
|
||||
addCard(Zone.LIBRARY, playerA, lion, 10);
|
||||
|
||||
setStopAt(1, PhaseStep.UPKEEP);
|
||||
execute();
|
||||
|
||||
assertExileCount(playerA, 0);
|
||||
assertLibraryCount(playerA, 10);
|
||||
assertLibraryCount(playerA, lion, 10);
|
||||
}
|
||||
|
||||
private void finalPaymentTest(int lionInLibrary, boolean replaced) {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
skipInitShuffling();
|
||||
removeAllCardsFromLibrary(playerA);
|
||||
addCard(Zone.LIBRARY, playerA, lion, lionInLibrary);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, ashiok);
|
||||
addCard(Zone.BATTLEFIELD, playerB, lion);
|
||||
addCard(Zone.HAND, playerA, finalPayment);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Scrubland", 2);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, finalPayment, lion);
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20 - (replaced ? 0 : 5));
|
||||
assertPermanentCount(playerB, lion, 0);
|
||||
assertExileCount(playerA, replaced ? 5 : 0);
|
||||
assertLibraryCount(playerA, lionInLibrary - (replaced ? 5 : 0));
|
||||
assertGraveyardCount(playerB, lion, 1); // Lion
|
||||
assertGraveyardCount(playerA, finalPayment, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finalPayment_0() {
|
||||
finalPaymentTest(0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finalPayment_4() {
|
||||
finalPaymentTest(4, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finalPayment_5() {
|
||||
finalPaymentTest(5, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finalPayment_10() {
|
||||
finalPaymentTest(10, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ReplacementCitadel() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
skipInitShuffling();
|
||||
removeAllCardsFromLibrary(playerA);
|
||||
addCard(Zone.LIBRARY, playerA, lion, 10);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, ashiok);
|
||||
addCard(Zone.BATTLEFIELD, playerA, citadel);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lion);
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertPermanentCount(playerA, lion, 1);
|
||||
assertExileCount(playerA, 2);
|
||||
assertLibraryCount(playerA, 10 - 2 - 1); // Lion was cast from there, and 2 cards were exiled as payment.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ReplacementLurkingEvil() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
skipInitShuffling();
|
||||
removeAllCardsFromLibrary(playerA);
|
||||
int libraryCount = 18;
|
||||
int exileCount = 0;
|
||||
int lifeCount = 20;
|
||||
addCard(Zone.LIBRARY, playerA, lion, libraryCount);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, ashiok);
|
||||
addCard(Zone.BATTLEFIELD, playerA, lurking);
|
||||
|
||||
activateAbility(1, PhaseStep.UPKEEP, playerA, "Pay half your life, rounded up:");
|
||||
libraryCount -= 10;
|
||||
exileCount += 10;
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, lifeCount);
|
||||
assertExileCount(playerA, exileCount);
|
||||
assertLibraryCount(playerA, libraryCount);
|
||||
|
||||
activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Pay half your life, rounded up:");
|
||||
lifeCount -= 10;
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, lifeCount);
|
||||
assertExileCount(playerA, exileCount);
|
||||
assertLibraryCount(playerA, libraryCount);
|
||||
|
||||
activateAbility(1, PhaseStep.END_TURN, playerA, "Pay half your life, rounded up:");
|
||||
libraryCount -= 5;
|
||||
exileCount += 5;
|
||||
|
||||
setStopAt(2, PhaseStep.UPKEEP);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, lifeCount);
|
||||
assertExileCount(playerA, exileCount);
|
||||
assertLibraryCount(playerA, libraryCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ReplacementPoet() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
skipInitShuffling();
|
||||
removeAllCardsFromLibrary(playerA);
|
||||
addCard(Zone.LIBRARY, playerA, lion, 10);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, ashiok);
|
||||
addCard(Zone.BATTLEFIELD, playerA, poet);
|
||||
|
||||
attack(1, playerA, poet);
|
||||
setChoice(playerA, true);// Yes to pay 2 life, those are replaced by cards exiled.
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20);
|
||||
assertExileCount(playerA, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TokensTrigger() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
skipInitShuffling();
|
||||
removeAllCardsFromLibrary(playerA);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
// Alternating, so choices are possible on Ashiok's +1
|
||||
addCard(Zone.LIBRARY, playerA, lion);
|
||||
addCard(Zone.LIBRARY, playerA, poet);
|
||||
}
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, ashiok);
|
||||
addCard(Zone.BATTLEFIELD, playerA, poet);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2:");
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, "Nightmare Token", 1, 1);
|
||||
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:");
|
||||
addTarget(playerA, poet);
|
||||
// 2 tokens, so stacking trigger.
|
||||
setChoice(playerA, "At the beginning of combat on your turn, if a card was put "
|
||||
+ "into exile this turn, put a +1/+1 counter on this creature.");
|
||||
|
||||
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, "Nightmare Token", 2, 2);
|
||||
|
||||
activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:");
|
||||
addTarget(playerA, lion);
|
||||
// 2 tokens, so stacking trigger.
|
||||
setChoice(playerA, "At the beginning of combat on your turn, if a card was put "
|
||||
+ "into exile this turn, put a +1/+1 counter on this creature.");
|
||||
|
||||
setStopAt(5, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, "Nightmare Token", 3, 3);
|
||||
|
||||
setStopAt(7, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, "Nightmare Token", 3, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void Ultimate() {
|
||||
setStrictChooseMode(true);
|
||||
|
||||
skipInitShuffling();
|
||||
removeAllCardsFromLibrary(playerA);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
// Alternating, so choices are possible on Ashiok's +1
|
||||
addCard(Zone.LIBRARY, playerA, lion);
|
||||
addCard(Zone.LIBRARY, playerA, lurking);
|
||||
}
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, ashiok);
|
||||
addCard(Zone.BATTLEFIELD, playerA, poet);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:");
|
||||
addTarget(playerA, lion);
|
||||
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:");
|
||||
addTarget(playerA, lurking);
|
||||
|
||||
activateAbility(5, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:");
|
||||
addTarget(playerA, lurking);
|
||||
|
||||
activateAbility(7, PhaseStep.PRECOMBAT_MAIN, playerA, "-7:", playerB);
|
||||
|
||||
setStopAt(7, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertExileCount(playerB, 2 + 3 + 3);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package mage.abilities.condition.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.game.Game;
|
||||
import mage.watchers.common.CardsExiledThisTurnWatcher;
|
||||
|
||||
/**
|
||||
* Checks if at least one card was put into exile this turn.
|
||||
* <p>
|
||||
* /!\ Need the CardsExiledThisTurnWatcher to be set up.
|
||||
*
|
||||
* @author Susucr
|
||||
*/
|
||||
public enum WasCardExiledThisTurnCondition implements Condition {
|
||||
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
CardsExiledThisTurnWatcher watcher = game.getState().getWatcher(CardsExiledThisTurnWatcher.class);
|
||||
return watcher != null && watcher.getCountCardsExiledThisTurn() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "a card was put into exile this turn";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package mage.abilities.dynamicvalue.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.hint.ValueHint;
|
||||
import mage.cards.Card;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public enum TotalCardsExiledOwnedManaValue implements DynamicValue {
|
||||
instance;
|
||||
|
||||
private static final Hint hint = new ValueHint("Total mana value of cards you own in exile", instance);
|
||||
|
||||
private TotalCardsExiledOwnedManaValue() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TotalCardsExiledOwnedManaValue copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||
int totalCMC = 0;
|
||||
List<Card> cards = game.getExile().getAllCards(
|
||||
game,
|
||||
sourceAbility.getControllerId()
|
||||
);
|
||||
for (Card card : cards) {
|
||||
totalCMC += card.getManaValue();
|
||||
}
|
||||
return totalCMC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "the total mana value of cards you own in exile";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "X";
|
||||
}
|
||||
|
||||
public static Hint getHint() {
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.CardsImpl;
|
||||
|
|
@ -11,29 +14,24 @@ import mage.players.Player;
|
|||
import mage.util.CardUtil;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
* @author LevelX2, Susucr
|
||||
*/
|
||||
public class ExileCardsFromTopOfLibraryTargetEffect extends OneShotEffect {
|
||||
|
||||
int amount;
|
||||
String targetName;
|
||||
private final DynamicValue amount;
|
||||
|
||||
public ExileCardsFromTopOfLibraryTargetEffect(int amount) {
|
||||
this(amount, null);
|
||||
this(StaticValue.get(amount));
|
||||
}
|
||||
|
||||
public ExileCardsFromTopOfLibraryTargetEffect(int amount, String targetName) {
|
||||
public ExileCardsFromTopOfLibraryTargetEffect(DynamicValue amount) {
|
||||
super(Outcome.Exile);
|
||||
this.amount = amount;
|
||||
this.staticText = (targetName == null ? "that player" : targetName) + " exiles the top "
|
||||
+ CardUtil.numberToText(amount, "")
|
||||
+ (amount == 1 ? "card" : " cards") + " of their library";
|
||||
this.amount = amount.copy();
|
||||
}
|
||||
|
||||
protected ExileCardsFromTopOfLibraryTargetEffect(final ExileCardsFromTopOfLibraryTargetEffect effect) {
|
||||
super(effect);
|
||||
this.amount = effect.amount;
|
||||
|
||||
this.amount = effect.amount.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -43,12 +41,24 @@ public class ExileCardsFromTopOfLibraryTargetEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
int milled = amount.calculate(game, source, this);
|
||||
Player targetPlayer = game.getPlayer(getTargetPointer().getFirst(game, source));
|
||||
if (targetPlayer != null) {
|
||||
if (milled > 0 && targetPlayer != null) {
|
||||
Cards cards = new CardsImpl();
|
||||
cards.addAllCards(targetPlayer.getLibrary().getTopCards(game, amount));
|
||||
cards.addAllCards(targetPlayer.getLibrary().getTopCards(game, milled));
|
||||
return targetPlayer.moveCards(cards, Zone.EXILED, source, game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(Mode mode) {
|
||||
if (staticText != null && !staticText.isEmpty()) {
|
||||
return staticText;
|
||||
}
|
||||
return getTargetPointer().describeTargets(mode.getTargets(), "that player")
|
||||
+ " exiles the top "
|
||||
+ (amount.toString().equals("1") ? "card" : CardUtil.numberToText(amount.toString(), "a") + " cards")
|
||||
+ " of their library";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public class Exile implements Serializable, Copyable<Exile> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return exiled cards from specific player. Use it in effects to find all cards in range.
|
||||
* Return exiled cards owned by a specific player. Use it in effects to find all cards in range.
|
||||
*
|
||||
* @param game
|
||||
* @param fromPlayerId
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ public class GameEvent implements Serializable {
|
|||
PLANESWALK, PLANESWALKED,
|
||||
PAID_CUMULATIVE_UPKEEP,
|
||||
DIDNT_PAY_CUMULATIVE_UPKEEP,
|
||||
LIFE_PAID,
|
||||
PAY_LIFE, LIFE_PAID,
|
||||
CASCADE_LAND,
|
||||
LEARN,
|
||||
//permanent events
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package mage.game.permanent.token;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.BeginningOfCombatTriggeredAbility;
|
||||
import mage.abilities.condition.common.WasCardExiledThisTurnCondition;
|
||||
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
|
||||
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||
import mage.abilities.hint.ConditionHint;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.TargetController;
|
||||
import mage.counters.CounterType;
|
||||
|
||||
/**
|
||||
* @author Susucr
|
||||
*/
|
||||
public final class AshiokWickedManipulatorNightmareToken extends TokenImpl {
|
||||
|
||||
private static final Hint hint = new ConditionHint(WasCardExiledThisTurnCondition.instance);
|
||||
|
||||
/**
|
||||
* /!\ You need to add CardsExiledThisTurnWatcher to any card using this token
|
||||
*/
|
||||
public AshiokWickedManipulatorNightmareToken() {
|
||||
super("Nightmare Token", "1/1 black Nightmare creature tokens with \"At the beginning of combat on your turn, if a card was put into exile this turn, put a +1/+1 counter on this creature.\"");
|
||||
cardType.add(CardType.CREATURE);
|
||||
color.setBlack(true);
|
||||
subtype.add(SubType.NIGHTMARE);
|
||||
power = new MageInt(1);
|
||||
toughness = new MageInt(1);
|
||||
|
||||
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
|
||||
new BeginningOfCombatTriggeredAbility(
|
||||
new AddCountersSourceEffect(CounterType.P1P1.createInstance()),
|
||||
TargetController.YOU,
|
||||
false
|
||||
),
|
||||
WasCardExiledThisTurnCondition.instance,
|
||||
"At the beginning of combat on your turn, if a card was put into exile "
|
||||
+ "this turn, put a +1/+1 counter on this creature."
|
||||
).addHint(hint));
|
||||
}
|
||||
|
||||
private AshiokWickedManipulatorNightmareToken(final AshiokWickedManipulatorNightmareToken token) {
|
||||
super(token);
|
||||
}
|
||||
|
||||
public AshiokWickedManipulatorNightmareToken copy() {
|
||||
return new AshiokWickedManipulatorNightmareToken(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1496,6 +1496,11 @@ public final class CardUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PAY_LIFE, player.getId(), source, player.getId(), lifeToPay))) {
|
||||
// 2023-08-20: For now, Cost being replaced are paid.
|
||||
// Waiting on actual ruling of Ashiok, Wicked Manipulator.
|
||||
return true;
|
||||
}
|
||||
if (player.loseLife(lifeToPay, game, source, false) >= lifeToPay) {
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LIFE_PAID, player.getId(), source, player.getId(), lifeToPay));
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package mage.watchers.common;
|
||||
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
/**
|
||||
* @author Susucr
|
||||
*/
|
||||
public class CardsExiledThisTurnWatcher extends Watcher {
|
||||
|
||||
private int countExiled = 0;
|
||||
|
||||
public CardsExiledThisTurnWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.ZONE_CHANGE
|
||||
&& ((ZoneChangeEvent) event).getToZone() == Zone.EXILED) {
|
||||
countExiled++;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCountCardsExiledThisTurn() {
|
||||
return countExiled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
countExiled = 0;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue