[40K] Implement The Ruinous Powers; fix Fires of Mount Doom (#12722)

* Add starting point: have exile top w/ any color cast, right duration

* Use random opponent

* Add delayed triggered ability

* rename MageIdentifier to AlternateCast

* Fix FiresOfMountDoomDelayedTriggeredAbility

* Add test for Fires of Mount Doom

* Add test for The Ruinous Powers

* Null checks
This commit is contained in:
jimga150 2024-08-24 18:37:00 -04:00 committed by GitHub
parent 9fe5d6bd1b
commit 060551ab48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 252 additions and 13 deletions

View file

@ -1,5 +1,6 @@
package mage.cards.f;
import mage.MageIdentifier;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
@ -50,7 +51,8 @@ public final class FiresOfMountDoom extends CardImpl {
this.addAbility(new SimpleActivatedAbility(
Zone.BATTLEFIELD,
new FiresOfMountDoomEffect(),
new ManaCostsImpl<>("{2}{R}")));
new ManaCostsImpl<>("{2}{R}")
).setIdentifier(MageIdentifier.FiresOfMountDoomAlternateCast));
}
private FiresOfMountDoom(final FiresOfMountDoom card) {
@ -68,7 +70,7 @@ class FiresOfMountDoomEffect extends OneShotEffect {
FiresOfMountDoomEffect() {
super(Outcome.Benefit);
this.staticText = "exile the top card of your library. You may play that card this turn. " +
"When you play a card this way, Fires of Mount Doom deals 2 damage to each player";
"When you play a card this way, {this} deals 2 damage to each player";
}
private FiresOfMountDoomEffect(final FiresOfMountDoomEffect effect) {
@ -100,19 +102,9 @@ class FiresOfMountDoomEffect extends OneShotEffect {
}
}
// TODO: this is not quite right in corner cases.
// Inspired by Havengul Lich which I feel has similar problems.
// For instance what if the card is [[Squee, the Immortal]] and
// is cast with squee AsThought.
// Or if the card is played with the AsThought, then replayed
// during the same turn. With current code, that would trigger
// incorrectly again.
// Is mor the solution there? Or having a way to get the specific
// used AsThought and having a way to identify that it was the
// same one as the FiresOfMountDoomEffect makeCardPlayable.
class FiresOfMountDoomDelayedTriggeredAbility extends DelayedTriggeredAbility {
private UUID cardId;
private final UUID cardId;
public FiresOfMountDoomDelayedTriggeredAbility(UUID cardId) {
super(new DamagePlayersEffect(2, TargetController.ANY), Duration.EndOfTurn);
@ -133,6 +125,9 @@ class FiresOfMountDoomDelayedTriggeredAbility extends DelayedTriggeredAbility {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.hasApprovingIdentifier(MageIdentifier.FiresOfMountDoomAlternateCast)){
return false;
}
return event.getSourceId().equals(cardId);
}

View file

@ -0,0 +1,138 @@
package mage.cards.t;
import java.util.*;
import mage.MageIdentifier;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.cards.*;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.util.CardUtil;
import mage.util.RandomUtil;
/**
*
* @author jimga150
*/
public final class TheRuinousPowers extends CardImpl {
public TheRuinousPowers(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}{R}");
// At the beginning of your upkeep, choose an opponent at random. Exile the top card of that player's library.
// Until end of turn, you may play that card and you may spend mana as though it were mana of any color to cast it.
// When you cast a spell this way, its owner loses life equal to its mana value.
Ability ability = new BeginningOfUpkeepTriggeredAbility(new TheRuinousPowersEffect(), TargetController.YOU, false)
.setIdentifier(MageIdentifier.TheRuinousPowersAlternateCast);
this.addAbility(ability);
}
private TheRuinousPowers(final TheRuinousPowers card) {
super(card);
}
@Override
public TheRuinousPowers copy() {
return new TheRuinousPowers(this);
}
}
// Based on YouFindSomePrisonersEffect
class TheRuinousPowersEffect extends OneShotEffect {
TheRuinousPowersEffect() {
super(Outcome.Benefit);
staticText = "choose an opponent at random. Exile the top card of that player's library. Until end of turn, " +
"you may play that card and you may spend mana as though it were mana of any color to cast it. " +
"When you cast a spell this way, its owner loses life equal to its mana value.";
}
private TheRuinousPowersEffect(final TheRuinousPowersEffect effect) {
super(effect);
}
@Override
public TheRuinousPowersEffect copy() {
return new TheRuinousPowersEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null){
return false;
}
List<UUID> opponents = new ArrayList<>(game.getOpponents(source.getControllerId()));
if (opponents.isEmpty()) {
return false;
}
Player opponent = game.getPlayer(opponents.get(RandomUtil.nextInt(opponents.size())));
if (opponent == null) {
return false;
}
Card card = opponent.getLibrary().getFromTop(game);
player.moveCards(card, Zone.EXILED, source, game);
if (card != null) {
CardUtil.makeCardPlayable(game, source, card, false, Duration.EndOfTurn, true);
game.addDelayedTriggeredAbility(new TheRuinousPowersTriggeredAbility(card.getId()), source);
}
return true;
}
}
// Based on FiresOfMountDoomDelayedTriggeredAbility
class TheRuinousPowersTriggeredAbility extends DelayedTriggeredAbility {
private final UUID cardId;
public TheRuinousPowersTriggeredAbility(UUID cardId) {
super(null, Duration.EndOfTurn);
this.cardId = cardId;
}
private TheRuinousPowersTriggeredAbility(final TheRuinousPowersTriggeredAbility ability) {
super(ability);
this.cardId = ability.cardId;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.SPELL_CAST;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!event.getSourceId().equals(cardId)){
return false;
}
if (!event.hasApprovingIdentifier(MageIdentifier.TheRuinousPowersAlternateCast)){
return false;
}
Card card = game.getCard(cardId);
if (card == null){
return false;
}
Player owner = game.getPlayer(card.getOwnerId());
if (owner == null){
return false;
}
owner.loseLife(card.getManaValue(), game, this, false);
return true;
}
@Override
public TheRuinousPowersTriggeredAbility copy() {
return new TheRuinousPowersTriggeredAbility(this);
}
@Override
public String getRule() {
return "When you cast a spell this way, its owner loses life equal to its mana value.";
}
}

View file

@ -265,6 +265,7 @@ public final class Warhammer40000 extends ExpansionSet {
cards.add(new SetCardInfo("The Golden Throne", 157, Rarity.RARE, mage.cards.t.TheGoldenThrone.class));
cards.add(new SetCardInfo("The Horus Heresy", 126, Rarity.RARE, mage.cards.t.TheHorusHeresy.class));
cards.add(new SetCardInfo("The Lost and the Damned", 129, Rarity.UNCOMMON, mage.cards.t.TheLostAndTheDamned.class));
cards.add(new SetCardInfo("The Ruinous Powers", 139, Rarity.RARE, mage.cards.t.TheRuinousPowers.class));
cards.add(new SetCardInfo("The Swarmlord", 4, Rarity.MYTHIC, mage.cards.t.TheSwarmlord.class));
cards.add(new SetCardInfo("The War in Heaven", 69, Rarity.RARE, mage.cards.t.TheWarInHeaven.class));
cards.add(new SetCardInfo("Their Name Is Death", 62, Rarity.RARE, mage.cards.t.TheirNameIsDeath.class));

View file

@ -0,0 +1,64 @@
package org.mage.test.cards.single.ltr;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author jimga150
*/
public class FiresOfMountDoomTest extends CardTestPlayerBase {
/**
* {@link mage.cards.f.FiresOfMountDoom Fires of Mount Doom}
* Legendary Enchantment
* When Fires of Mount Doom enters the battlefield, it deals 2 damage to target creature an opponent controls.
* Destroy all Equipment attached to that creature.
* {2}{R}: Exile the top card of your library. You may play that card this turn.
* When you play a card this way, Fires of Mount Doom deals 2 damage to each player.
*/
private static final String fires = "Fires of Mount Doom";
@Test
public void test_castWithDamage() {
// Test the damaging triggered ability
addCard(Zone.BATTLEFIELD, playerA, fires, 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
addCard(Zone.LIBRARY, playerA, "Squee, the Immortal", 1);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{R}");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Squee, the Immortal");
setChoice(playerA, fires); // Choose Fires as the approving object
skipInitShuffling();
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Squee, the Immortal", 1);
assertLife(playerB, currentGame.getStartingLife() - 2);
}
@Test
public void test_castNoDamage() {
// Test that the damaging triggered ability doesnt trigger if something else allows the exiled card to be cast
addCard(Zone.BATTLEFIELD, playerA, fires, 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
addCard(Zone.LIBRARY, playerA, "Squee, the Immortal", 1);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{R}");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Squee, the Immortal");
setChoice(playerA, "Squee"); // Choose Squee as the approving object (which casts it normally)
skipInitShuffling();
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Squee, the Immortal", 1);
assertLife(playerB, currentGame.getStartingLife());
}
}

View file

@ -0,0 +1,39 @@
package org.mage.test.cards.single.ltr;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author jimga150
*/
public class TheRuinousPowersTest extends CardTestPlayerBase {
/**
* {@link mage.cards.t.TheRuinousPowers The Ruinous Powers}
* Enchantment
* At the beginning of your upkeep, choose an opponent at random. Exile the top card of that players library.
* Until end of turn, you may play that card and you may spend mana as though it were mana of any color to cast it.
* When you cast a spell this way, its owner loses life equal to its mana value.
*/
private static final String powers = "The Ruinous Powers";
@Test
public void test_castWithDamage() {
addCard(Zone.BATTLEFIELD, playerA, powers, 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
addCard(Zone.LIBRARY, playerB, "Squee, the Immortal", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Squee, the Immortal");
skipInitShuffling();
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Squee, the Immortal", 1);
assertLife(playerB, currentGame.getStartingLife() - 3);
}
}

View file

@ -72,6 +72,8 @@ public enum MageIdentifier {
WithoutPayingManaCostAlternateCast,
AlurenAlternateCast,
OfferingAlternateCast,
TheRuinousPowersAlternateCast,
FiresOfMountDoomAlternateCast,
PrimalPrayersAlternateCast;
/**