fix Grinning Totem

the exiled card wouldn't be put in its owner's graveyard if Grinning Totem
changed zones before the delayed trigger

any cards using CardUtil.getCardExileZoneId for delayed effects are likely
broken in the same way
This commit is contained in:
Neil Gentleman 2015-11-22 17:53:18 -08:00
parent 230d1d37bd
commit 085b00499c
3 changed files with 40 additions and 14 deletions

View file

@ -58,7 +58,6 @@ import mage.target.TargetSpell;
import mage.util.CardUtil;
/**
* import mage.constants.Outcome;
*
* @author LevelX2
*/

View file

@ -38,7 +38,6 @@ import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.constants.AsThoughEffectType;
@ -70,13 +69,11 @@ public class GrinningTotem extends CardImpl {
// {2}, {tap}, Sacrifice Grinning Totem: Search target opponent's library for a card and exile it. Then that player shuffles his or her library.
// Until the beginning of your next upkeep, you may play that card.
// At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new GrinningTotemSearchAndExileEffect(), new ManaCostsImpl("{2}"));
ability.addCost(new TapSourceCost());
ability.addCost(new SacrificeSourceCost());
ability.addTarget(new TargetOpponent());
// At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard.
ability.addEffect(new CreateDelayedTriggeredAbilityEffect(new GrinningTotemDelayedTriggeredAbility()));
this.addAbility(ability);
}
@ -94,7 +91,9 @@ class GrinningTotemSearchAndExileEffect extends OneShotEffect {
public GrinningTotemSearchAndExileEffect() {
super(Outcome.Benefit);
this.staticText = "Search target opponent's library for a card and exile it. Then that player shuffles his or her library. Until the beginning of your next upkeep, you may play that card";
this.staticText = "Search target opponent's library for a card and exile it. Then that player shuffles his or her library. " +
"Until the beginning of your next upkeep, you may play that card. " +
"At the beginning of your next upkeep, if you haven't played it, put it into its owner's graveyard";
}
public GrinningTotemSearchAndExileEffect(final GrinningTotemSearchAndExileEffect effect) {
@ -110,18 +109,21 @@ class GrinningTotemSearchAndExileEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player you = game.getPlayer(source.getControllerId());
Player targetOpponent = game.getPlayer(source.getFirstTarget());
MageObject sourcObject = game.getObject(source.getSourceId());
MageObject sourceObject = game.getObject(source.getSourceId());
if (you != null && targetOpponent != null) {
if (targetOpponent.getLibrary().size() > 0) {
TargetCardInLibrary targetCard = new TargetCardInLibrary();
if (you.searchLibrary(targetCard, game, targetOpponent.getId())) {
Card card = targetOpponent.getLibrary().remove(targetCard.getFirstTarget(), game);
if (card != null) {
you.moveCardToExileWithInfo(card, CardUtil.getCardExileZoneId(game, source), sourcObject != null ? sourcObject.getIdName() : "", source.getSourceId(), game, Zone.LIBRARY, true);
UUID exileZoneId = CardUtil.getCardExileZoneId(game, source);
you.moveCardToExileWithInfo(card, exileZoneId, sourceObject != null ? sourceObject.getIdName() : "", source.getSourceId(), game, Zone.LIBRARY, true);
ContinuousEffect effect = new GrinningTotemMayPlayEffect();
effect.setTargetPointer(new FixedTarget(card.getId()));
game.addEffect(effect, source);
}
game.addDelayedTriggeredAbility(new GrinningTotemDelayedTriggeredAbility(exileZoneId), source);
}
}
}
targetOpponent.shuffleLibrary(game);
@ -173,17 +175,21 @@ class GrinningTotemMayPlayEffect extends AsThoughEffectImpl {
class GrinningTotemDelayedTriggeredAbility extends DelayedTriggeredAbility {
public GrinningTotemDelayedTriggeredAbility() {
super(new GrinningTotemPutIntoGraveyardEffect());
private final UUID exileZoneId;
public GrinningTotemDelayedTriggeredAbility(UUID exileZoneId) {
super(new GrinningTotemPutIntoGraveyardEffect(exileZoneId));
this.exileZoneId = exileZoneId;
}
public GrinningTotemDelayedTriggeredAbility(final GrinningTotemDelayedTriggeredAbility ability) {
super(ability);
this.exileZoneId = ability.exileZoneId;
}
@Override
public boolean checkInterveningIfClause(Game game) {
ExileZone exileZone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, this.getSourceId()));
ExileZone exileZone = game.getExile().getExileZone(exileZoneId);
return exileZone != null && exileZone.getCards(game).size() > 0;
}
@ -210,13 +216,17 @@ class GrinningTotemDelayedTriggeredAbility extends DelayedTriggeredAbility {
class GrinningTotemPutIntoGraveyardEffect extends OneShotEffect {
public GrinningTotemPutIntoGraveyardEffect() {
private final UUID exileZoneId;
public GrinningTotemPutIntoGraveyardEffect(UUID exileZoneId) {
super(Outcome.Detriment);
this.exileZoneId = exileZoneId;
this.staticText = "put it into its owner's graveyard";
}
public GrinningTotemPutIntoGraveyardEffect(final GrinningTotemPutIntoGraveyardEffect effect) {
super(effect);
this.exileZoneId = effect.exileZoneId;
}
@Override
@ -227,7 +237,7 @@ class GrinningTotemPutIntoGraveyardEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
ExileZone zone = game.getExile().getExileZone(CardUtil.getCardExileZoneId(game, source));
ExileZone zone = game.getExile().getExileZone(exileZoneId);
if (controller != null && zone != null) {
return controller.moveCards(zone, Zone.EXILED, Zone.GRAVEYARD, source, game);
}

View file

@ -21,4 +21,21 @@ public class GrinningTotemTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, 1); // the exiled Mountain
}
@Test
public void testCardsGoToGraveyard2() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
addCard(Zone.BATTLEFIELD, playerA, "Grinning Totem");
addCard(Zone.BATTLEFIELD, playerB, "Tormod's Crypt");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2},{T}, Sacrifice {this}: Search target opponent's library for a card and exile it", playerB);
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "{T}, Sacrifice {this}: Exile all cards", playerA);
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, 0);
assertGraveyardCount(playerB, 2); // the exiled Mountain and Tormod's Crypt
}
}