rework warp implementation (#13874)

This commit is contained in:
Evan Kranzler 2025-07-22 16:32:41 -04:00 committed by GitHub
parent 8aafba38b9
commit cdf23ac18a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 57 additions and 22 deletions

View file

@ -1,6 +1,5 @@
package mage.cards.f; package mage.cards.f;
import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect;
@ -17,7 +16,6 @@ import mage.game.permanent.Permanent;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.targetpointer.FixedTarget; import mage.target.targetpointer.FixedTarget;
import java.util.Collections;
import java.util.UUID; import java.util.UUID;
/** /**
@ -63,11 +61,7 @@ class FullBoreEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null if (permanent == null || !WarpAbility.checkIfPermanentWarped(permanent, game)) {
|| !game
.getPermanentCostsTags()
.getOrDefault(new MageObjectReference(permanent, game, -1), Collections.emptyMap())
.containsKey(WarpAbility.WARP_ACTIVATION_VALUE_KEY)) {
return false; return false;
} }
game.addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance()) game.addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance())

View file

@ -1,6 +1,7 @@
package mage.abilities.keyword; package mage.abilities.keyword;
import mage.MageIdentifier; import mage.MageIdentifier;
import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.SpellAbility; import mage.abilities.SpellAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
@ -10,14 +11,33 @@ import mage.abilities.effects.OneShotEffect;
import mage.cards.Card; import mage.cards.Card;
import mage.constants.*; import mage.constants.*;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil; import mage.util.CardUtil;
import mage.watchers.Watcher;
import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
* 702.185. Warp
* <p>
* 702.185a Warp represents two static abilities that function while the card with warp is on the stack,
* one of which may create a delayed triggered ability. Warp [cost] means
* You may cast this card from your hand by paying [cost] rather than its mana cost and
* If this spells warp cost was paid, exile the permanent this spell becomes at the beginning of the next end step.
* Its owner may cast this card after the current turn has ended for as long as it remains exiled.
* Casting a spell for its warp cost follows the rules for paying alternative costs in rules 601.2b and 601.2fh.
* <p>
* 702.185b Some effects refer to warped cards in exile. A warped card in exile is one
* that was exiled by the delayed triggered ability created by a warp ability.
* <p>
* 702.185c Some effects refer to whether a spell was warped this turn.
* This means that a spell was cast for its warp cost this turn.
*
* @author TheElk801 * @author TheElk801
*/ */
public class WarpAbility extends SpellAbility { public class WarpAbility extends SpellAbility {
@ -41,6 +61,7 @@ public class WarpAbility extends SpellAbility {
this.addCost(new ManaCostsImpl<>(manaString)); this.addCost(new ManaCostsImpl<>(manaString));
this.setAdditionalCostsRuleVisible(false); this.setAdditionalCostsRuleVisible(false);
this.allowGraveyard = allowGraveyard; this.allowGraveyard = allowGraveyard;
this.addWatcher(new WarpAbilityWatcher());
} }
private WarpAbility(final WarpAbility ability) { private WarpAbility(final WarpAbility ability) {
@ -48,15 +69,6 @@ public class WarpAbility extends SpellAbility {
this.allowGraveyard = ability.allowGraveyard; this.allowGraveyard = ability.allowGraveyard;
} }
// The ability sets up a delayed trigger which can't be set up using the cost tag system
public static void addDelayedTrigger(SpellAbility spellAbility, Game game) {
if (spellAbility instanceof WarpAbility) {
game.addDelayedTriggeredAbility(
new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new WarpExileEffect()), spellAbility
);
}
}
@Override @Override
public ActivationStatus canActivate(UUID playerId, Game game) { public ActivationStatus canActivate(UUID playerId, Game game) {
switch (game.getState().getZone(getSourceId())) { switch (game.getState().getZone(getSourceId())) {
@ -104,6 +116,13 @@ public class WarpAbility extends SpellAbility {
public static String makeWarpString(UUID playerId) { public static String makeWarpString(UUID playerId) {
return playerId + "- Warped"; return playerId + "- Warped";
} }
public static boolean checkIfPermanentWarped(Permanent permanent, Game game) {
return permanent != null
&& game.getPermanentCostsTags()
.getOrDefault(new MageObjectReference(permanent, game, -1), Collections.emptyMap())
.containsKey(WarpAbility.WARP_ACTIVATION_VALUE_KEY);
}
} }
class WarpExileEffect extends OneShotEffect { class WarpExileEffect extends OneShotEffect {
@ -122,8 +141,9 @@ class WarpExileEffect extends OneShotEffect {
} }
} }
WarpExileEffect() { WarpExileEffect(Permanent permanent, Game game) {
super(Outcome.Benefit); super(Outcome.Benefit);
this.setTargetPointer(new FixedTarget(permanent, game));
staticText = "exile this creature if it was cast for its warp cost"; staticText = "exile this creature if it was cast for its warp cost";
} }
@ -138,9 +158,12 @@ class WarpExileEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId()); Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
Permanent permanent = game.getPermanent(source.getSourceId()); if (permanent == null) {
if (permanent == null || permanent.getZoneChangeCounter(game) != source.getSourceObjectZoneChangeCounter() + 1) { return false;
}
Player player = game.getPlayer(permanent.getOwnerId());
if (player == null) {
return false; return false;
} }
player.moveCardsToExile( player.moveCardsToExile(
@ -155,3 +178,23 @@ class WarpExileEffect extends OneShotEffect {
return true; return true;
} }
} }
class WarpAbilityWatcher extends Watcher {
WarpAbilityWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() != GameEvent.EventType.ENTERS_THE_BATTLEFIELD) {
return;
}
Permanent permanent = game.getPermanent(event.getTargetId());
if (WarpAbility.checkIfPermanentWarped(permanent, game)) {
game.addDelayedTriggeredAbility(
new AtTheBeginOfNextEndStepDelayedTriggeredAbility(new WarpExileEffect(permanent, game)), permanent.getSpellAbility()
);
}
}
}

View file

@ -8,7 +8,6 @@ import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.keyword.BestowAbility; import mage.abilities.keyword.BestowAbility;
import mage.abilities.keyword.PrototypeAbility; import mage.abilities.keyword.PrototypeAbility;
import mage.abilities.keyword.TransformAbility; import mage.abilities.keyword.TransformAbility;
import mage.abilities.keyword.WarpAbility;
import mage.cards.*; import mage.cards.*;
import mage.constants.*; import mage.constants.*;
import mage.counters.Counter; import mage.counters.Counter;
@ -422,7 +421,6 @@ public class Spell extends StackObjectImpl implements Card {
} else { } else {
MageObjectReference mor = new MageObjectReference(getSpellAbility()); MageObjectReference mor = new MageObjectReference(getSpellAbility());
game.storePermanentCostsTags(mor, getSpellAbility()); game.storePermanentCostsTags(mor, getSpellAbility());
WarpAbility.addDelayedTrigger(getSpellAbility(), game);
return controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null); return controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null);
} }
} }