I've almost got
Nivmagus Elemental working. Only problems are with copied spells [Gatherer ruling (hilariously dated 2004

)]
You can exile a copy of an instant or sorcery spell to activate
Nivmagus Elemental’s ability if you control the copy.
My test cases are
Hunting Pack and a
Firebolt imprinted on
Isochron Scepter.
The first mission was getting the copied spells to show up in the list of choices, but eventually got that right. Then had a problem where if you activated the Nivmagus using the original (in the case of the
Hunting Pack) then you wouldn't be able to activate using any of the copies.
Now the only issue I have left is that the copied stack instances aren't exiled. Can I post a patch, or should I commit and someone can look at it then revert if there's no saving it?
With regards to the AI, it won't use the ability unless you cast something in response to a spell it has on the stack... then it will use it every time. It actually works out quite well because often it means you're countering the spell in which case it's a waste anyway. But added the RemAIDeck:True flag anyway.
Edit:
- Nivmagus Elemental | Open
- Name:Nivmagus Elemental
ManaCost:UR
Types:Creature Elemental
Text:no text
PT:1/2
A:AB$ PutCounter | Cost$ ExileFromStack<1/Spell.Instant+YouCtrl;Spell.Sorcery+YouCtrl/instant or sorcery spell> | CostDesc$ Exile an instant or sorcery spell you control: | CounterType$ P1P1 | CounterNum$ 2 | SpellDescription$ Put two +1/+1 counters on CARDNAME. (That spell won't resolve.)
SVar:RemAIDeck:True
SVar:Rarity:Rare
SVar:Picture:http://www.wizards.com/global/images/magic/general/nivmagus_elemental.jpg
SetInfo:RTR|Rare|http://magiccards.info/scans/en/rtr/219.jpg
Oracle:Exile an instant or sorcery spell you control: Put two +1/+1 counters on Nivmagus Elemental. (That spell won't resolve.)
End
- Patch | Open
- Index: src/main/java/forge/card/cost/Cost.java
===================================================================
--- src/main/java/forge/card/cost/Cost.java (revision 17191)
+++ src/main/java/forge/card/cost/Cost.java (working copy)
@@ -161,6 +161,7 @@
private static final String EXILE_STR = "Exile<";
private static final String EXILE_FROM_HAND_STR = "ExileFromHand<";
private static final String EXILE_FROM_GRAVE_STR = "ExileFromGrave<";
+ private static final String EXILE_FROM_STACK_STR = "ExileFromStack<";
private static final String EXILE_FROM_TOP_STR = "ExileFromTop<";
private static final String RETURN_STR = "Return<";
private static final String REVEAL_STR = "Reveal<";
@@ -305,6 +306,14 @@
this.costParts.add(new CostExile(splitStr[0], splitStr[1], description, ZoneType.Graveyard));
}
+ while (parse.contains(Cost.EXILE_FROM_STACK_STR)) {
+ final String[] splitStr = this.abCostParse(parse, Cost.EXILE_FROM_STACK_STR, 3);
+ parse = this.abUpdateParse(parse, Cost.EXILE_FROM_STACK_STR);
+
+ final String description = splitStr.length > 2 ? splitStr[2] : null;
+ this.costParts.add(new CostExile(splitStr[0], splitStr[1], description, ZoneType.Stack));
+ }
+
while (parse.contains(Cost.EXILE_FROM_TOP_STR)) {
final String[] splitStr = this.abCostParse(parse, Cost.EXILE_FROM_TOP_STR, 3);
parse = this.abUpdateParse(parse, Cost.EXILE_FROM_TOP_STR);
Index: src/main/java/forge/card/cost/CostExile.java
===================================================================
--- src/main/java/forge/card/cost/CostExile.java (revision 17191)
+++ src/main/java/forge/card/cost/CostExile.java (working copy)
@@ -17,6 +17,7 @@
*/
package forge.card.cost;
+import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.JOptionPane;
@@ -28,6 +29,7 @@
import forge.Singletons;
import forge.card.abilityfactory.AbilityFactory;
import forge.card.spellability.SpellAbility;
+import forge.card.spellability.SpellAbilityStackInstance;
import forge.control.input.Input;
import forge.game.player.ComputerUtil;
import forge.game.player.Player;
@@ -142,7 +144,14 @@
*/
@Override
public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost) {
- CardList typeList = activator.getCardsIn(this.getFrom());
+ CardList typeList = new CardList();
+ if (this.getFrom().equals(ZoneType.Stack)) {
+ for (int i = 0; i < AllZone.getStack().size(); i++) {
+ typeList.add(AllZone.getStack().peekAbility(i).getSourceCard());
+ }
+ } else {
+ typeList = activator.getCardsIn(this.getFrom());
+ }
if (!this.getThis()) {
typeList = typeList.getValidCards(this.getType().split(";"), activator, source);
@@ -167,6 +176,15 @@
public final void payAI(final SpellAbility ability, final Card source, final CostPayment payment) {
for (final Card c : this.getList()) {
Singletons.getModel().getGameAction().exile(c);
+ if (this.from.equals(ZoneType.Stack)) {
+ ArrayList<SpellAbility> spells = c.getSpellAbilities();
+ for (SpellAbility spell : spells) {
+ if (c.isInZone(ZoneType.Exile)) {
+ final SpellAbilityStackInstance si = AllZone.getStack().getInstanceFromSpellAbility(spell);
+ AllZone.getStack().remove(si);
+ }
+ }
+ }
}
}
@@ -316,9 +334,18 @@
this.done();
}
- this.typeList = sa.getActivatingPlayer().getCardsIn(part.getFrom());
- this.typeList = this.typeList.getValidCards(type.split(";"), sa.getActivatingPlayer(),
- sa.getSourceCard());
+ if (part.getFrom().equals(ZoneType.Stack)) {
+ this.typeList = new CardList();
+ for (int i = 0; i < AllZone.getStack().size(); i++) {
+ this.typeList.add(AllZone.getStack().peekAbility(i).getSourceCard());
+ this.typeList = this.typeList.getValidCards(type.split(";"), sa.getActivatingPlayer(),
+ sa.getSourceCard());
+ }
+ } else {
+ this.typeList = sa.getActivatingPlayer().getCardsIn(part.getFrom());
+ this.typeList = this.typeList.getValidCards(type.split(";"), sa.getActivatingPlayer(),
+ sa.getSourceCard());
+ }
for (int i = 0; i < nNeeded; i++) {
if (this.typeList.size() == 0) {
@@ -336,6 +363,15 @@
if (i == (nNeeded - 1)) {
this.done();
}
+ if (part.getFrom().equals(ZoneType.Stack)) {
+ ArrayList<SpellAbility> spells = c.getSpellAbilities();
+ if (c.isInZone(ZoneType.Exile)) {
+ for (SpellAbility spell : spells) {
+ final SpellAbilityStackInstance si = AllZone.getStack().getInstanceFromSpellAbility(spell);
+ AllZone.getStack().remove(si);
+ }
+ }
+ }
} else {
this.cancel();
break;
@@ -403,6 +439,8 @@
if (part.getFrom().equals(ZoneType.Hand)) {
msg.append(" from your Hand");
+ } else if (part.getFrom().equals(ZoneType.Stack)) {
+ msg.append(" from the Stack");
}
this.typeList = sa.getActivatingPlayer().getCardsIn(part.getFrom());
this.typeList = this.typeList.getValidCards(type.split(";"), sa.getActivatingPlayer(),
Index: src/main/java/forge/card/cost/CostUtil.java
===================================================================
--- src/main/java/forge/card/cost/CostUtil.java (revision 17191)
+++ src/main/java/forge/card/cost/CostUtil.java (working copy)
@@ -37,7 +37,7 @@
*/
public class CostUtil {
private static Random r = new Random();
-
+
/**
* Check sacrifice cost.
*
@@ -457,8 +457,7 @@
// Just a shortcut..
AllZone.getInputControl().setInput(in, true);
}
-
-
+
public static Cost combineCosts(Cost cost1, Cost cost2) {
if (cost1 == null) {
if (cost2 == null) {
@@ -467,11 +466,11 @@
return cost2;
}
}
-
+
if (cost2 == null) {
return cost1;
}
-
+
for (final CostPart part : cost1.getCostParts()) {
if (!(part instanceof CostMana)) {
cost2.getCostParts().add(part);
EDIT2: I noticed in testing that
Counterspell also can't counter storm copies. Is this meant to be the case?