Reworked Suspend ability: (#13527)

* Updated Delay and Gandalf Of The Secret Fire to get the main card since they target spells

* Suspend now properly lets you play either side of mdfc and spell parts from adventure/omen cards utilizing CardUtil.castSpellWithAttributesForFree method

* Removed extra code in SuspendPlayCardEffect since the referenced bug for Epochrasite does not seem to appear. Removed related gainedTemporary variable also.

* Added tests for Omen and Suspend With Taigam, Master Opportunists as well as an Epochrasite test for recasting after suspend.
This commit is contained in:
Jmlundeen 2025-04-11 06:22:13 -05:00 committed by GitHub
parent 8dd8953a85
commit 1b06813997
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 158 additions and 43 deletions

View file

@ -1,6 +1,5 @@
package mage.abilities.keyword;
import mage.ApprovingObject;
import mage.MageIdentifier;
import mage.abilities.Ability;
import mage.abilities.SpecialAction;
@ -17,8 +16,11 @@ import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.counter.RemoveCounterSourceEffect;
import mage.cards.Card;
import mage.cards.CardsImpl;
import mage.cards.ModalDoubleFacedCard;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
@ -26,8 +28,6 @@ import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@ -112,7 +112,6 @@ import java.util.UUID;
public class SuspendAbility extends SpecialAction {
private final String ruleText;
private boolean gainedTemporary;
/**
* Gives the card the SuspendAbility
@ -177,6 +176,11 @@ public class SuspendAbility extends SpecialAction {
* or added by Jhoira of the Ghitu
*/
public static void addSuspendTemporaryToCard(Card card, Ability source, Game game) {
if (card instanceof ModalDoubleFacedCard) {
// Need to ensure the suspend ability gets put on the left side card
// since counters get added to this card.
card = ((ModalDoubleFacedCard) card).getLeftHalfCard();
}
SuspendAbility ability = new SuspendAbility(0, null, card, false);
ability.setSourceId(card.getId());
ability.setControllerId(card.getOwnerId());
@ -206,7 +210,6 @@ public class SuspendAbility extends SpecialAction {
private SuspendAbility(final SuspendAbility ability) {
super(ability);
this.ruleText = ability.ruleText;
this.gainedTemporary = ability.gainedTemporary;
}
@Override
@ -232,10 +235,6 @@ public class SuspendAbility extends SpecialAction {
return ruleText;
}
public boolean isGainedTemporary() {
return gainedTemporary;
}
@Override
public SuspendAbility copy() {
return new SuspendAbility(this);
@ -345,40 +344,16 @@ class SuspendPlayCardEffect extends OneShotEffect {
if (player == null || card == null) {
return false;
}
if (!player.chooseUse(Outcome.Benefit, "Play " + card.getLogName() + " without paying its mana cost?", source, game)) {
// ensure we're getting the main card when passing to CardUtil to check all parts of card
// MDFC points to left half card
card = card.getMainCard();
// cast/play the card for free
if (!CardUtil.castSpellWithAttributesForFree(player, source, game, new CardsImpl(card),
StaticFilters.FILTER_CARD, null, true)) {
return true;
}
// remove temporary suspend ability (used e.g. for Epochrasite)
// TODO: isGainedTemporary is not set or use in other places, so it can be deleted?!
List<Ability> abilitiesToRemove = new ArrayList<>();
for (Ability ability : card.getAbilities(game)) {
if (ability instanceof SuspendAbility && (((SuspendAbility) ability).isGainedTemporary())) {
abilitiesToRemove.add(ability);
}
}
if (!abilitiesToRemove.isEmpty()) {
for (Ability ability : card.getAbilities(game)) {
if (ability instanceof SuspendBeginningOfUpkeepInterveningIfTriggeredAbility
|| ability instanceof SuspendPlayCardAbility) {
abilitiesToRemove.add(ability);
}
}
// remove the abilities from the card
// TODO: will not work with Adventure Cards and another auto-generated abilities list
// TODO: is it work after blink or return to hand?
/*
bug example:
Epochrasite bug: It comes out of suspend, is cast and enters the battlefield. THEN if it's returned to
its owner's hand from battlefield, the bounced Epochrasite can't be cast for the rest of the game.
*/
card.getAbilities().removeAll(abilitiesToRemove);
}
// cast the card for free
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
boolean cardWasCast = player.cast(player.chooseAbilityForCast(card, game, true),
game, true, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
if (cardWasCast && (card.isCreature(game))) {
// creatures cast from suspend gain haste
if ((card.isCreature(game))) {
ContinuousEffect effect = new GainHasteEffect();
effect.setTargetPointer(new FixedTarget(card.getId(), card.getZoneChangeCounter(game) + 1));
game.addEffect(effect, source);