I've just finished making it possible to use ChangeZone targeting spells on the stack.

Everything that was needed was almost there in CounterSpells, it just needed to be tweaked and ported. I've tested with
Venser, Shaper Savant (as well as some other normal changeZone cards to make sure I didn't break it) and everything seems to work. At this stage its just for the human player, but I first wanted some experienced eyes checking what I've done so far to point out any errors and pitfalls. Here's a patch for AbilityFactoryChangeZone and the script for Venser:
- Patch | Open
- Code: Select all
### Eclipse Workspace Patch 1.0
#P Forge
Index: src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java
===================================================================
--- src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java (revision 15148)
+++ src/main/java/forge/card/abilityfactory/AbilityFactoryChangeZone.java (working copy)
@@ -1888,6 +1888,8 @@
*/
private static void changeKnownOriginResolve(final AbilityFactory af, final SpellAbility sa) {
ArrayList<Card> tgtCards;
+ ArrayList<SpellAbility> sas;
+
final HashMap<String, String> params = af.getMapParams();
final Target tgt = sa.getTarget();
final Player player = sa.getActivatingPlayer();
@@ -1905,6 +1907,28 @@
}
}
+ // changing zones for spells on the stack
+ if (tgt != null) {
+ sas = tgt.getTargetSAs();
+ } else {
+ sas = AbilityFactory.getDefinedSpellAbilities(sa.getSourceCard(), params.get("Defined"), sa);
+ }
+
+ for (final SpellAbility tgtSA : sas) {
+ final Card tgtSACard = tgtSA.getSourceCard();
+
+ if (tgtSA.isSpell() && !CardFactoryUtil.isCounterable(tgtSACard)) {
+ continue;
+ }
+
+ final SpellAbilityStackInstance si = AllZone.getStack().getInstanceFromSpellAbility(tgtSA);
+ if (si == null) {
+ continue;
+ }
+
+ removeFromStack(tgtSA, sa, si);
+ } // End of change from stack
+
final String remember = params.get("RememberChanged");
final String imprint = params.get("Imprint");
@@ -2043,6 +2067,54 @@
return ret;
}
+ /**
+ * <p>
+ * removeFromStack.
+ * </p>
+ *
+ * @param tgtSA
+ * a {@link forge.card.spellability.SpellAbility} object.
+ * @param srcSA
+ * a {@link forge.card.spellability.SpellAbility} object.
+ * @param si
+ * a {@link forge.card.spellability.SpellAbilityStackInstance}
+ * object.
+ */
+ private static void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si) {
+ AllZone.getStack().remove(si);
+
+ final AbilityFactory af = srcSA.getAbilityFactory();
+ final HashMap<String, String> params = af.getMapParams();
+
+ if (params.containsKey("Destination")) {
+ if (tgtSA.isAbility()) {
+ // Shouldn't be able to target Abilities on the stack
+ } else if (tgtSA.isFlashBackAbility()) {
+ Singletons.getModel().getGameAction().exile(tgtSA.getSourceCard());
+ } else if (params.get("Destination").equals("Graveyard")) {
+ Singletons.getModel().getGameAction().moveToGraveyard(tgtSA.getSourceCard());
+ } else if (params.get("Destination").equals("Exile")) {
+ Singletons.getModel().getGameAction().exile(tgtSA.getSourceCard());
+ } else if (params.get("Destination").equals("TopOfLibrary")) {
+ Singletons.getModel().getGameAction().moveToLibrary(tgtSA.getSourceCard());
+ } else if (params.get("Destination").equals("Hand")) {
+ Singletons.getModel().getGameAction().moveToHand(tgtSA.getSourceCard());
+ } else if (params.get("Destination").equals("BottomOfLibrary")) {
+ Singletons.getModel().getGameAction().moveToBottomOfLibrary(tgtSA.getSourceCard());
+ } else if (params.get("Destination").equals("ShuffleIntoLibrary")) {
+ Singletons.getModel().getGameAction().moveToBottomOfLibrary(tgtSA.getSourceCard());
+ tgtSA.getSourceCard().getController().shuffle();
+ } else {
+ throw new IllegalArgumentException("AbilityFactory_ChangeZone: Invalid Destination argument for card "
+ + srcSA.getSourceCard().getName());
+ }
+
+ if (!tgtSA.isAbility()) {
+ System.out.println("Moving spell to " + params.get("Destination"));
+ }
+ }
+ }
+
// *************************************************************************************
// ************************** ChangeZoneAll
// ********************************************
- Venser, Shaper Savant | Open
- Name:Venser, Shaper Savant
ManaCost:2 U U
Types:Legendary Creature Human Wizard
Text:no text
PT:2/2
K:Flash
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ ChooseTgtMode | TriggerDescription$ When CARDNAME enters the battlefield, return target spell or permanent to its owner's hand.
SVar:ChooseTgtMode:AB$ Charm | Cost$ 0 | Defined$ You | Choices$ BounceSpell,BouncePermanent
SVar:BounceSpell:DB$ChangeZone | TargetType$ Spell | ValidTgts$ Card | TgtZone$ Stack | Origin$ Stack | Destination$ Hand | SpellDescription$ Return target spell to its owner's hand.
SVar:BouncePermanent:DB$ChangeZone | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return target permanent to its owner's hand.
SVar:RemAIDeck:True
SVar:Rarity:Rare
SVar:Picture:http://www.wizards.com/global/images/magic/general/venser_shaper_savant.jpg
SetInfo:FUT|Rare|http://magiccards.info/scans/en/fut/46.jpg
Oracle:Flash (You may cast this spell any time you could cast an instant.)\nWhen Venser, Shaper Savant enters the battlefield, return target spell or permanent to its owner's hand.
End
If its almost right I'll make any changes you suggest, then I'll need to add something for the AI (I will need help there

). The script probably needs to be tweaked slightly too because you the can currently cancel out of the triggered ability. Probably just a mandatory true flag or two needed.
EDIT: Of course it could be completely wrong and painful to trained eyes in which case feel free to tell me to just scratch the idea.