mirror of
https://github.com/correl/mage.git
synced 2024-11-14 03:00:10 +00:00
* Shifting Shadows - Fixed not proper handling of gained triggered abilities during step resolution of Shifting Shadows effect (fixes #6571).
This commit is contained in:
parent
f65f4a4344
commit
07386cce8d
6 changed files with 316 additions and 7 deletions
|
@ -46,10 +46,12 @@ public final class MairsilThePretender extends CardImpl {
|
|||
this.power = new MageInt(4);
|
||||
this.toughness = new MageInt(4);
|
||||
|
||||
// When Mairsil, the Pretender enters the battlefield, you may exile an artifact or creature card from your hand or graveyard and put a cage counter on it.
|
||||
// When Mairsil, the Pretender enters the battlefield, you may exile an artifact or creature card from your hand
|
||||
// or graveyard and put a cage counter on it.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new MairsilThePretenderExileEffect(), true));
|
||||
|
||||
// Mairsil, the Pretender has all activated abilities of all cards you own in exile with cage counters on them. You may activate each of those abilities only once each turn.
|
||||
// Mairsil, the Pretender has all activated abilities of all cards you own in exile with cage counters on them.
|
||||
// You may activate each of those abilities only once each turn.
|
||||
Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new MairsilThePretenderGainAbilitiesEffect());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
|
|
@ -71,12 +71,12 @@ public final class ShiftingShadow extends CardImpl {
|
|||
|
||||
class ShiftingShadowEffect extends OneShotEffect {
|
||||
|
||||
private UUID auraId;
|
||||
private final UUID auraId;
|
||||
|
||||
public ShiftingShadowEffect(UUID auraId) {
|
||||
super(Outcome.PutCreatureInPlay);
|
||||
this.staticText = "destroy this creature. Reveal cards from the top of your library until you reveal a creature card. "
|
||||
+ "Put that card onto the battlefield and attach Shifting Shadow to it, then put all other cards revealed this way on the bottom of your library in a random order";
|
||||
+ "Put that card onto the battlefield and attach {this} to it, then put all other cards revealed this way on the bottom of your library in a random order";
|
||||
this.auraId = auraId;
|
||||
}
|
||||
|
||||
|
@ -107,6 +107,11 @@ class ShiftingShadowEffect extends OneShotEffect {
|
|||
}
|
||||
if (aura != null) {
|
||||
enchanted.destroy(source.getSourceId(), game, false);
|
||||
// Because this effect has two steps, we have to call the processAction method here, so that triggered effects of the target going to graveyard go to the stack
|
||||
// If we don't do it here, gained triggered effects to the target will be removed from the following moveCards method and the applyEffcts done there.
|
||||
// Example: {@link org.mage.test.commander.duel.MairsilThePretenderTest#MairsilThePretenderTest Test}
|
||||
game.getState().processAction(game);
|
||||
|
||||
Cards revealed = new CardsImpl();
|
||||
Cards otherCards = new CardsImpl();
|
||||
for (Card card : controller.getLibrary().getCards(game)) {
|
||||
|
|
90
Mage.Tests/CommanderDuel_Mairisil_UBR.dck
Normal file
90
Mage.Tests/CommanderDuel_Mairisil_UBR.dck
Normal file
|
@ -0,0 +1,90 @@
|
|||
1 [DGM:11] Aetherling
|
||||
1 [JUD:77] Anger
|
||||
1 [10E:66] Arcanis the Omnipotent
|
||||
1 [SOM:28] Argent Sphinx
|
||||
1 [CHK:52] Azami, Lady of Scrolls
|
||||
1 [AVR:47] Deadeye Navigator
|
||||
1 [DRK:44] Eater of the Dead
|
||||
1 [EXO:33] Ertai, Wizard Adept
|
||||
1 [ALA:43] Fatestitcher
|
||||
1 [SOM:64] Geth, Lord of the Vault
|
||||
1 [EVE:55] Hateflayer
|
||||
1 [DKA:139] Havengul Lich
|
||||
1 [10E:87] Horseshoe Crab
|
||||
1 [ICE:136] Infernal Denizen
|
||||
1 [M12:59] Jace's Archivist
|
||||
1 [SHM:42] Knacksaw Clique
|
||||
1 [OGW:4] Kozilek, the Great Distortion
|
||||
1 [PLC:43] Magus of the Bazaar
|
||||
1 [ICE:150] Minion of Leshrac
|
||||
1 [SOM:72] Necrotic Ooze
|
||||
1 [SHM:258] Pili-Pala
|
||||
1 [MRD:47] Quicksilver Elemental
|
||||
1 [SOK:53] Sakashima the Impostor
|
||||
1 [MIR:142] Shauku, Endbringer
|
||||
1 [M15:231] Soul of New Phyrexia
|
||||
1 [PLC:110] Torchling
|
||||
1 [EMN:109] Tree of Perdition
|
||||
1 [M20:64] Leyline of Anticipation
|
||||
1 [9ED:152] Phyrexian Arena
|
||||
1 [PCY:45] Rhystic Study
|
||||
1 [RNA:245] Blood Crypt
|
||||
1 [KTK:230] Bloodstained Mire
|
||||
1 [WWK:132] Bojuka Bog
|
||||
1 [ELD:333] Command Tower
|
||||
1 [ALA:222] Crumbling Necropolis
|
||||
1 [RAV:276] Dimir Aqueduct
|
||||
1 [CON:142] Exotic Orchard
|
||||
1 [CHK:277] Hall of the Bandit Lord
|
||||
8 [IKO:263] Island
|
||||
1 [GPT:159] Izzet Boilerworks
|
||||
1 [CHK:279] Minamo, School at Water's Edge
|
||||
3 [IKO:269] Mountain
|
||||
1 [C20:298] Path of Ancestry
|
||||
1 [KTK:239] Polluted Delta
|
||||
1 [M19:254] Reliquary Tower
|
||||
1 [ONS:322] Riptide Laboratory
|
||||
1 [ORI:251] Shivan Reef
|
||||
1 [GRN:257] Steam Vents
|
||||
1 [10E:359] Sulfurous Springs
|
||||
1 [BFZ:249] Sunken Hollow
|
||||
4 [IKO:266] Swamp
|
||||
1 [10E:362] Underground River
|
||||
1 [GRN:259] Watery Grave
|
||||
1 [ODY:118] Buried Alive
|
||||
1 [3ED:105] Demonic Tutor
|
||||
1 [USG:188] Gamble
|
||||
1 [AVR:151] Reforge the Soul
|
||||
1 [EMA:108] Toxic Deluge
|
||||
1 [MB1:1603] Mana Crypt
|
||||
1 [5ED:388] Mana Vault
|
||||
1 [HOU:165] Mirage Mirror
|
||||
1 [5ED:391] Nevinyrral's Disk
|
||||
1 [CHK:268] Sensei's Divining Top
|
||||
1 [3ED:274] Sol Ring
|
||||
1 [5DN:156] Staff of Domination
|
||||
1 [LRW:263] Thousand-Year Elixir
|
||||
1 [UDS:139] Thran Dynamo
|
||||
1 [5DN:163] Vedalken Orrery
|
||||
1 [TMP:55] Capsize
|
||||
1 [C20:146] Chaos Warp
|
||||
1 [RTR:35] Cyclonic Rift
|
||||
1 [ODY:132] Entomb
|
||||
1 [INV:57] Fact or Fiction
|
||||
1 [TMP:70] Intuition
|
||||
1 [THS:65] Swan Song
|
||||
1 [RTR:111] Vandalblast
|
||||
1 [3ED:185] Wheel of Fortune
|
||||
1 [GTC:207] Whispering Madness
|
||||
1 [USG:111] Windfall
|
||||
1 [SHM:248] Cauldron of Souls
|
||||
1 [RAV:260] Dimir Signet
|
||||
1 [9ED:297] Fellwar Stone
|
||||
1 [DOM:215] Gilded Lotus
|
||||
1 [ULG:126] Grim Monolith
|
||||
1 [GTC:231] Illusionist's Bracers
|
||||
1 [GPT:152] Izzet Signet
|
||||
1 [MRD:199] Lightning Greaves
|
||||
SB: 1 [C17:41] Mairsil, the Pretender
|
||||
LAYOUT MAIN:(1,7)(CARD_TYPE,false,50)|([SHM:258],[M15:231])([SHM:248],[RAV:260],[9ED:297],[DOM:215],[ULG:126],[GTC:231],[GPT:152],[MRD:199],[MB1:1603],[5ED:388],[HOU:165],[5ED:391],[CHK:268],[3ED:274],[5DN:156],[LRW:263],[UDS:139],[5DN:163])([DGM:11],[JUD:77],[10E:66],[SOM:28],[CHK:52],[AVR:47],[DRK:44],[EXO:33],[ALA:43],[SOM:64],[EVE:55],[DKA:139],[10E:87],[ICE:136],[M12:59],[SHM:42],[OGW:4],[PLC:43],[ICE:150],[SOM:72],[MRD:47],[SOK:53],[MIR:142],[PLC:110],[EMN:109])([M20:64],[9ED:152],[PCY:45])([TMP:55],[C20:146],[RTR:35],[ODY:132],[INV:57],[TMP:70],[THS:65])([RNA:245],[KTK:230],[WWK:132],[ELD:333],[ALA:222],[RAV:276],[CON:142],[CHK:277],[IKO:263],[IKO:263],[IKO:263],[IKO:263],[IKO:263],[IKO:263],[IKO:263],[IKO:263],[GPT:159],[CHK:279],[IKO:269],[IKO:269],[IKO:269],[C20:298],[KTK:239],[M19:254],[ONS:322],[ORI:251],[GRN:257],[10E:359],[BFZ:249],[IKO:266],[IKO:266],[IKO:266],[IKO:266],[10E:362],[GRN:259])([ODY:118],[3ED:105],[USG:188],[AVR:151],[EMA:108],[RTR:111],[3ED:185],[GTC:207],[USG:111])
|
||||
LAYOUT SIDEBOARD:(1,1)(NONE,false,50)|([C17:41])
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.mage.test.commander.duel;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.GameException;
|
||||
import org.junit.Test;
|
||||
import static org.mage.test.player.TestPlayer.NO_TARGET;
|
||||
import org.mage.test.serverside.base.CardTestCommanderDuelBase;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class MairsilThePretenderTest extends CardTestCommanderDuelBase {
|
||||
|
||||
@Override
|
||||
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
|
||||
// When Mairsil, the Pretender enters the battlefield, you may exile an artifact or creature card from your hand
|
||||
// or graveyard and put a cage counter on it.
|
||||
// Mairsil, the Pretender has all activated abilities of all cards you own in exile with cage counters on them.
|
||||
// You may activate each of those abilities only once each turn.
|
||||
setDecknamePlayerA("CommanderDuel_Mairisil_UBR.dck"); // Commander = Mairsil, the Pretender {1}{U}{B}{R}
|
||||
return super.createNewGameAndPlayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic Test
|
||||
*/
|
||||
@Test
|
||||
public void useShifitingShadowTest() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
|
||||
// Enchant creature
|
||||
// Enchanted creature has haste and “At the beginning of your upkeep, destroy this creature.
|
||||
// Reveal cards from the top of your library until you reveal a creature card.
|
||||
// Put that card onto the battlefield and attach Shifting Shadow to it,
|
||||
// then put all other cards revealed this way on the bottom of your library in a random order.”
|
||||
addCard(Zone.HAND, playerA, "Shifting Shadow", 1); // {2}{R}
|
||||
|
||||
// Tap: Draw three cards.
|
||||
// {2}{U}{U}: Return Arcanis the Omnipotent to its owner's hand.
|
||||
addCard(Zone.HAND, playerA, "Arcanis the Omnipotent", 1); // Creature {3}{U}{U}{U}
|
||||
|
||||
setChoice(playerA, "Yes");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mairsil, the Pretender");
|
||||
setChoice(playerA, "Yes"); // Exile a card
|
||||
setChoice(playerA, "Arcanis the Omnipotent");
|
||||
|
||||
|
||||
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Shifting Shadow", "Mairsil, the Pretender");
|
||||
|
||||
setChoice(playerA, "Yes"); // Move commander to command zone
|
||||
setStopAt(5, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertLife(playerA, 40);
|
||||
assertLife(playerB, 40);
|
||||
|
||||
assertExileCount("Arcanis the Omnipotent", 1);
|
||||
|
||||
assertCommandZoneCount(playerA, "Mairsil, the Pretender", 1);
|
||||
|
||||
assertPermanentCount(playerA, "Shifting Shadow", 1);
|
||||
}
|
||||
/**
|
||||
* I tried playing it in Mairsil the Pretender commander deck and found 2
|
||||
* cases where the creature is not properly destroyed:
|
||||
*
|
||||
* Using Arcanis the Omnipotent ability to return my commander to hand while
|
||||
* trigger is on the stack I got Mairsil to hand then got asked whether I
|
||||
* want to put him in gy so I answered yes assuming it cannot be destroyed
|
||||
* while in my hand. He got put in graveyard straight from my hand.
|
||||
* According to Oracle rules I should get a creature from top of the deck
|
||||
* and still have my commander in hand. After giving my commander undying
|
||||
* with shifting shadow trigger on the stack he got put in gy with undying
|
||||
* not triggering.
|
||||
*
|
||||
* All this points to the card being hardcoded to put the creature into
|
||||
* graveyard instead of simply destroying it
|
||||
*/
|
||||
@Test
|
||||
public void useShifitingShadowAndArcanisTest() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 3);
|
||||
skipInitShuffling();
|
||||
// Enchant creature
|
||||
// Enchanted creature has haste and “At the beginning of your upkeep, destroy this creature.
|
||||
// Reveal cards from the top of your library until you reveal a creature card.
|
||||
// Put that card onto the battlefield and attach Shifting Shadow to it,
|
||||
// then put all other cards revealed this way on the bottom of your library in a random order.”
|
||||
addCard(Zone.HAND, playerA, "Shifting Shadow", 1); // {2}{R}
|
||||
|
||||
// Tap: Draw three cards.
|
||||
// {2}{U}{U}: Return Arcanis the Omnipotent to its owner's hand.
|
||||
addCard(Zone.HAND, playerA, "Arcanis the Omnipotent", 1); // Creature {3}{U}{U}{U}
|
||||
|
||||
setChoice(playerA, "Yes");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mairsil, the Pretender");
|
||||
setChoice(playerA, "Yes"); // Exile a card
|
||||
setChoice(playerA, "Arcanis the Omnipotent");
|
||||
|
||||
|
||||
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Shifting Shadow", "Mairsil, the Pretender");
|
||||
|
||||
activateAbility(5, PhaseStep.UPKEEP, playerA, "{2}{U}{U}: Return", NO_TARGET, "At the beginning of your upkeep");
|
||||
setChoice(playerA, "No"); // Don't move commander to command zone because it goes to hand
|
||||
|
||||
setStopAt(5, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertLife(playerA, 40);
|
||||
assertLife(playerB, 40);
|
||||
|
||||
assertExileCount("Arcanis the Omnipotent", 1);
|
||||
|
||||
assertCommandZoneCount(playerA, "Mairsil, the Pretender", 0);
|
||||
assertHandCount(playerA, "Mairsil, the Pretender", 1);
|
||||
|
||||
|
||||
assertGraveyardCount(playerA, "Shifting Shadow", 1); // Goes to graveyard because commander left battlefield to hand from Arcanis ability
|
||||
assertPermanentCount(playerA, "Silvercoat Lion", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useShifitingShadowAndEndlingTest() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||
addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 3);
|
||||
skipInitShuffling();
|
||||
// Enchant creature
|
||||
// Enchanted creature has haste and “At the beginning of your upkeep, destroy this creature.
|
||||
// Reveal cards from the top of your library until you reveal a creature card.
|
||||
// Put that card onto the battlefield and attach Shifting Shadow to it,
|
||||
// then put all other cards revealed this way on the bottom of your library in a random order.”
|
||||
addCard(Zone.HAND, playerA, "Shifting Shadow", 1); // {2}{R}
|
||||
// Tap: Draw three cards.
|
||||
// {2}{U}{U}: Return Arcanis the Omnipotent to its owner's hand.
|
||||
addCard(Zone.HAND, playerA, "Arcanis the Omnipotent", 1); // Creature {3}{U}{U}{U}
|
||||
// {B}: Endling gains menace until end of turn.
|
||||
// {B}: Endling gains deathtouch until end of turn.
|
||||
// {B}: Endling gains undying until end of turn.
|
||||
// {1}: Endling gets +1/-1 or -1/+1 until end of turn.
|
||||
addCard(Zone.HAND, playerA, "Endling", 1); // Creature {3}{U}{U}{U}
|
||||
|
||||
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mairsil, the Pretender");
|
||||
setChoice(playerA, "Yes"); // Exile a card
|
||||
setChoice(playerA, "Yes"); // Exile from Hand
|
||||
setChoice(playerA, "Endling");
|
||||
|
||||
|
||||
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Shifting Shadow", "Mairsil, the Pretender");
|
||||
|
||||
activateAbility(5, PhaseStep.UPKEEP, playerA, "{B}: {this} gains Undying", NO_TARGET, "At the beginning of your upkeep");
|
||||
setChoice(playerA, "No"); // Don't move commander to command zone because can come back by Undying
|
||||
|
||||
setChoice(playerA, "Yes"); // Exile a card (Mairsil comes back from Undying)
|
||||
setChoice(playerA, "Yes"); // Exile from hand
|
||||
setChoice(playerA, "Arcanis the Omnipotent");
|
||||
|
||||
setStopAt(5, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertLife(playerA, 40);
|
||||
assertLife(playerB, 40);
|
||||
|
||||
assertExileCount("Endling", 1);
|
||||
assertExileCount("Arcanis the Omnipotent", 1);
|
||||
|
||||
assertCommandZoneCount(playerA, "Mairsil, the Pretender", 0);
|
||||
assertGraveyardCount(playerA, "Mairsil, the Pretender", 0);
|
||||
assertPermanentCount(playerA, "Mairsil, the Pretender", 1); // Returns from Undying
|
||||
assertPowerToughness(playerA, "Mairsil, the Pretender", 5, 5);
|
||||
|
||||
assertPermanentCount(playerA, "Shifting Shadow", 1); // Enchants Silvercoat Lion
|
||||
assertPermanentCount(playerA, "Silvercoat Lion", 1);
|
||||
}
|
||||
|
||||
}
|
|
@ -1600,6 +1600,15 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
player.addAction(turnNum, step, ACTIVATE_ABILITY + ability + "$target=" + String.join("^", targetNames));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param turnNum
|
||||
* @param step
|
||||
* @param player
|
||||
* @param ability
|
||||
* @param targetName use NO_TARGET if there is no target to set
|
||||
* @param spellOnStack
|
||||
*/
|
||||
public void activateAbility(int turnNum, PhaseStep step, TestPlayer player, String ability, String targetName, String spellOnStack) {
|
||||
// TODO: it's uses computerPlayer to execute, only ability target will work, but choices and targets commands aren't
|
||||
this.activateAbility(turnNum, step, player, ability, targetName, spellOnStack, StackClause.WHILE_ON_STACK);
|
||||
|
|
|
@ -91,9 +91,9 @@ public class CommanderReplacementEffect extends ReplacementEffectImpl {
|
|||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
|
||||
|
||||
if (!game.isSimulation() && commanderId.equals(zEvent.getTargetId())) {
|
||||
//System.out.println("applies " + game.getTurnNum() + ": " + game.getObject(event.getTargetId()).getName() + ": " + zEvent.getFromZone() + " -> " + zEvent.getToZone() + "; " + game.getObject(zEvent.getSourceId()));
|
||||
}
|
||||
// if (!game.isSimulation() && commanderId.equals(zEvent.getTargetId())) {
|
||||
// System.out.println("applies " + game.getTurnNum() + ": " + game.getObject(event.getTargetId()).getName() + ": " + zEvent.getFromZone() + " -> " + zEvent.getToZone() + "; " + game.getObject(zEvent.getSourceId()));
|
||||
// }
|
||||
|
||||
if (zEvent.getToZone().equals(Zone.HAND) && !alsoHand) {
|
||||
return false;
|
||||
|
|
Loading…
Reference in a new issue