spDamageTgt - enhanced damage spell code
Post MTG Forge Related Programming Questions Here
Moderators: timmermac, Blacksmith, KrazyTheFox, Agetian, friarsol, CCGHQ Admins
29 posts
• Page 1 of 2 • 1, 2
spDamageTgt - enhanced damage spell code
by Rob Cashwalker » 14 Oct 2009, 15:50
As I've been mentioning a lot about how some simple changes to keyword handlers could make room for many new cards, here's what I've been working on. I've been writing this post offline for about 2 weeks now, tweaking the code and adding things I missed or realized a better method. I had a "blast" giving the computer the same deck I had and we blasted each other and our creatures with a few of the spells I converted and the 2 new spells I describe below.
After all of this work though, and some of the discussion in the CardFactory split thread, I realize some of this stuff may need to be further refined. For example, currently all the parameters have context by their position, but with a bit of tweaking, it could just as easily be made to work with a "parameter=value" style....
==========================================================================
Welcome to the next evolution of keyword scripting.
DamageTgt takes care of both creature-only, player-only and creature or player logic.
Given a creature damage spell, if the AI has Stuffy Doll, you better watch yourself.
Creatures on the human's side are weighted by more than just flying, and damage already dealt to creatures is considered.
But that's not important.
The real gem of this keyword submission is the groundwork laid with two pseudo functions developed alongside.
In place of the integers that define damage, you may put in a "Count$" function.
It will count the number of cards that meet certain conditions.
Usually phrased as "where X equals the number of ___" or "do ___ for each ___".
The cool part is that the functionality still works for explicit card code, until the keywords catch up. Just execute the actual code function, passing a string.
There are of course "drawbacks". Some spells don't come cheap. Others spells may have benefits.
doDrawBack$ will process a string that defines the effect.
If the scale of the effect depends on the value of Count$, just put an X in the number field.
Again, this could be used to streamline new explicit card code.
Another tweak I thought of is putting the Spell Description and Stack Description into the keyword string....
For simple damage spells, building the string works well enough.
With variable amounts of damage and possible drawbacks, one could argue why not just copy the card text..
A charm card has three distinct effects, each of which can be represented by one keyword.
However each one needs a SpellDescription in order to show up on the choice screen.
Syntax:
Citadel of Pain
Count$TypeUntappedOppCtrl.Land
Carpet of Flowers
Count$TypeOppCtrl.Island
Feast of Flesh
Count$NamedInAllYards.Feast of Flesh/Plus.1
An-Havva Inn
Count$GreenTypeOnBattlefield.Creature/Plus.1
Aspect of Wolf
Count$TypeYouCtrl.Forest/HalfUp
Drawback is formatted as follows:
Orcish Artillery
Drawback$DamageYou/3
Tendrils of Corruption
Drawback$YouGainLife/X
Thirst for Knowledge (spDraw needs to be updated first)
Drawback$YouDiscard/2/UnlessDiscardType.Artifact
Call to Heel
Drawback$TgtDraw/1
First Volley
Drawback$TgtDamage/1
Notice the "You" maybe in front of the action or after, whichever makes sense.
Card Definitions
Some examples from our existing library:
Char
2 R
Instant
no text
spDamgeTgtCP:4:Drawback$DamageYou/2:Char deals 4 damage to target creature or player and 2 damage to you.:Char deals 4 damage.
Lightning Helix
R W
Instant
no text
spDamageTgtCP:3:Drawback$YouGainLife/3:Lightning Helix deals 3 damage to target creature or player and you gain 3 life.:Lightning Helix - deals 3 damage and you gain 3 life.
Mob Justice
1 R
Sorcery
spDamageTgtP:Count$TypeYouCtrl.Creature:Mob Justice deals damage to target player equal to the number of creatures you control.:Mob Justice - deals damage to player.
Ember Shot
6 R
Instant
Draw a card.
spDamageTgtCP:3
Cantrip
Tendrils of Corruption
3 B
Instant
no text
spDamageTgtC:Count$TypeYouCtrl.Swamp:Drawback$YouGainLife/X:Tendrils of Corruption deals X damage to target creature and you gain X life, where X is the number of Swamps you control.:Tendrils of Corruption - deals damage and you gain life.
Corrupt
5 B
Sorcery
no text
spDamageTgtCP:Count$TypeYouCtrl.Swamp:Drawback$YouGainLife/X:Corrupt deals damage equal to the number of Swamps you control to target creature or player. You gain life equal to the damage dealt this way.:Corrupt deals damage and you gain life.
Psionic Blast
2 U
Instant
no text
spDamageTgtCP:4:Drawback$DamageYou/2:Psionic Blast deals 4 damage to target creature or player and 2 damage to you.
sionic Blast - deals 4 damage and 2 damage to you.
Tribal Flames
1 R
Sorcery
no text
spDamageTgtCP:Count$Domain:Tribal Flames deals X damage to target creature or player, where X is the number of basic land types among lands you control.:Tribal Flames - deals damage.
Cackling Flames
3 R
Instant
no text
spDamageTgtCP:Count$Hellbent.5.3:Cackling Flames deals 3 damage to target creature or player. Hellbent - Cackling Flames deals 5 damage to that creature or player instead if you have no cards in hand.: Cackling Flames deals damage.
Douse in Gloom
2 B
Instant
no text
spDamageTgtC:2:Drawback$YouGainLife/2:Douse in Gloom deals 2 damage to target creature and you gain 2 life.
ouse in Gloom - deals 2 damage and you gain 2 life.
Some new cards:
Feast of Flesh
B
no text
spDamageTgtC:Count$NamedInAllYards.Feast of Flesh/Plus.1:Drawback$YouGainLife/X:Feast of Flesh deals X damage to target creature and you gain X life, where X is 1 plus the number of cards named Feast of Flesh in all graveyards.:Feast of Flesh deals X damage.
First Volley
1 R
Instant - Arcane
no text
spDamageTgtC:1:Drawback$DamageTgt/1:First Volley deals 1 damage to target creature and 1 damage to that creature's controller.: First Volley - deals 1 damage to creature and 1 damage to its controller.
Code Changes:
CardFactory.java
Commentary
Other keywords can be similarly enhanced by following these bits of code from above:
By the way, the reason we need to use single element arrays (NumDmg[0]) is because the storage for the variable needs to be "final" and yet we need to be able to change the value dynamically.
At the end of resolve() add a drawback: (basically anytime an effect is tacked on to the primary effect, usually using the word "and" - Lightning Helix) (not two separate effects on one card - Ember Shot)
Note, don't include "Drawback$" in the DB parameter, just the part to the right of the "$".
cardController and Opp are straight forward.
TgtP must be set to either the target player, or the controller of the targeted permanent of the primary effect. ("that creature's controller")
The drawback routine doesn't currently make use of the Src card and TgtC card.
If there are other things to count, add them.
But they only have to be added in one place.
I am attaching my changed source files based on the 0828 beta release.
After all of this work though, and some of the discussion in the CardFactory split thread, I realize some of this stuff may need to be further refined. For example, currently all the parameters have context by their position, but with a bit of tweaking, it could just as easily be made to work with a "parameter=value" style....
==========================================================================
Welcome to the next evolution of keyword scripting.
DamageTgt takes care of both creature-only, player-only and creature or player logic.
Given a creature damage spell, if the AI has Stuffy Doll, you better watch yourself.
Creatures on the human's side are weighted by more than just flying, and damage already dealt to creatures is considered.
But that's not important.
The real gem of this keyword submission is the groundwork laid with two pseudo functions developed alongside.
In place of the integers that define damage, you may put in a "Count$" function.
It will count the number of cards that meet certain conditions.
Usually phrased as "where X equals the number of ___" or "do ___ for each ___".
The cool part is that the functionality still works for explicit card code, until the keywords catch up. Just execute the actual code function, passing a string.
There are of course "drawbacks". Some spells don't come cheap. Others spells may have benefits.
doDrawBack$ will process a string that defines the effect.
If the scale of the effect depends on the value of Count$, just put an X in the number field.
Again, this could be used to streamline new explicit card code.
Another tweak I thought of is putting the Spell Description and Stack Description into the keyword string....
For simple damage spells, building the string works well enough.
With variable amounts of damage and possible drawbacks, one could argue why not just copy the card text..
A charm card has three distinct effects, each of which can be represented by one keyword.
However each one needs a SpellDescription in order to show up on the choice screen.
Syntax:
- Code: Select all
spDamageTgt:#Dmg
spDamageTgt:#Dmg:Drawback
spDamageTgt:#Dmg:SpellDescription:StackDescription
spDamageTgt:#Dmg:Drawback:SpellDescription:StackDescription
- Code: Select all
Count$CountingDescription[.AdditionalFields]
Counting Descriptions:
Domain
YourLifeTotal / OppLifeTotal
Chroma.W / Chroma.U / Chroma.B / Chroma.R / Chroma.G
Hellbent.numHB.numNotHB
Or combinations of the following columns:
Quality SubQuality Location .AdditionalField
Named White YouCtrl .Name
Type Blue InYourYard .Type
Black InYourHand /Plus.# (X+#)
Red OppCtrl /Minus.# (X-#)
Green InOppYard /NMinus.# (#-X)
Multicolor InOppHand /Twice
Monocolor OnBattlefield /HalfUp
Tapped InAllYards /HalfDown
Untapped InAllHands
Citadel of Pain
Count$TypeUntappedOppCtrl.Land
Carpet of Flowers
Count$TypeOppCtrl.Island
Feast of Flesh
Count$NamedInAllYards.Feast of Flesh/Plus.1
An-Havva Inn
Count$GreenTypeOnBattlefield.Creature/Plus.1
Aspect of Wolf
Count$TypeYouCtrl.Forest/HalfUp
Drawback is formatted as follows:
- Code: Select all
Drawback$EffectPlayerLocation/{#Num|X}[/AdditionalField]
#Num is a number, (1, 2, 3...) or X, which is supplied as the nDB parameter of the actual function (like the result of Count$)
The effect is built using column combinations:
Effect Player Location Num Additional Field
Damage You #
GainLife Opp X
LoseLife Tgt (Either tgtPlayer or controller of tgtCard)
Discard UnlessDiscardType.Type
AtRandom
HandToLibrary Top
Bottom
TopOrBottom
Draw
GenToken (placeholder)
ReturnFrom Play (placeholder)
Yard
Sacrifice (placeholder) Type
Orcish Artillery
Drawback$DamageYou/3
Tendrils of Corruption
Drawback$YouGainLife/X
Thirst for Knowledge (spDraw needs to be updated first)
Drawback$YouDiscard/2/UnlessDiscardType.Artifact
Call to Heel
Drawback$TgtDraw/1
First Volley
Drawback$TgtDamage/1
Notice the "You" maybe in front of the action or after, whichever makes sense.
Card Definitions
Some examples from our existing library:
Char
2 R
Instant
no text
spDamgeTgtCP:4:Drawback$DamageYou/2:Char deals 4 damage to target creature or player and 2 damage to you.:Char deals 4 damage.
Lightning Helix
R W
Instant
no text
spDamageTgtCP:3:Drawback$YouGainLife/3:Lightning Helix deals 3 damage to target creature or player and you gain 3 life.:Lightning Helix - deals 3 damage and you gain 3 life.
Mob Justice
1 R
Sorcery
spDamageTgtP:Count$TypeYouCtrl.Creature:Mob Justice deals damage to target player equal to the number of creatures you control.:Mob Justice - deals damage to player.
Ember Shot
6 R
Instant
Draw a card.
spDamageTgtCP:3
Cantrip
Tendrils of Corruption
3 B
Instant
no text
spDamageTgtC:Count$TypeYouCtrl.Swamp:Drawback$YouGainLife/X:Tendrils of Corruption deals X damage to target creature and you gain X life, where X is the number of Swamps you control.:Tendrils of Corruption - deals damage and you gain life.
Corrupt
5 B
Sorcery
no text
spDamageTgtCP:Count$TypeYouCtrl.Swamp:Drawback$YouGainLife/X:Corrupt deals damage equal to the number of Swamps you control to target creature or player. You gain life equal to the damage dealt this way.:Corrupt deals damage and you gain life.
Psionic Blast
2 U
Instant
no text
spDamageTgtCP:4:Drawback$DamageYou/2:Psionic Blast deals 4 damage to target creature or player and 2 damage to you.

Tribal Flames
1 R
Sorcery
no text
spDamageTgtCP:Count$Domain:Tribal Flames deals X damage to target creature or player, where X is the number of basic land types among lands you control.:Tribal Flames - deals damage.
Cackling Flames
3 R
Instant
no text
spDamageTgtCP:Count$Hellbent.5.3:Cackling Flames deals 3 damage to target creature or player. Hellbent - Cackling Flames deals 5 damage to that creature or player instead if you have no cards in hand.: Cackling Flames deals damage.
Douse in Gloom
2 B
Instant
no text
spDamageTgtC:2:Drawback$YouGainLife/2:Douse in Gloom deals 2 damage to target creature and you gain 2 life.

Some new cards:
Feast of Flesh
B
no text
spDamageTgtC:Count$NamedInAllYards.Feast of Flesh/Plus.1:Drawback$YouGainLife/X:Feast of Flesh deals X damage to target creature and you gain X life, where X is 1 plus the number of cards named Feast of Flesh in all graveyards.:Feast of Flesh deals X damage.
First Volley
1 R
Instant - Arcane
no text
spDamageTgtC:1:Drawback$DamageTgt/1:First Volley deals 1 damage to target creature and 1 damage to that creature's controller.: First Volley - deals 1 damage to creature and 1 damage to its controller.
Code Changes:
CardFactory.java
- Code: Select all
private final int shouldSpDamageTgt(Card c) {
ArrayList<String> a = c.getKeyword();
for (int i = 0; i < a.size(); i++)
{
if (a.get(i).toString().startsWith("spDamageTgt"))
return i;
}
return -1;
}
- Code: Select all
if (shouldSpDamageTgt(card) != -1)
{
int n = shouldSpDamageTgt(card);
if (n != -1)
{
String parse = card.getKeyword().get(n).toString();
card.removeIntrinsicKeyword(parse);
card.clearSpellAbility();
String k[] = parse.split(":");
final boolean TgtCreature[] = {false};
final boolean TgtPlayer[] = {false};
final boolean TgtCP[] = {false};
if (k[0].contains("CP"))
TgtCP[0] = true;
else if (k[0].contains("P"))
TgtPlayer[0] = true;
else if (k[0].contains("C"))
TgtCreature[0] = true;
// how much damage
final int NumDmg[] = {-1};
final String NumDmgX[] = {"none"};
if (k[1].length() <= 2) // numeric
NumDmg[0] = Integer.parseInt(k[1]);
else // result of some sort of function
{
if (k[1].startsWith("Count$"))
{
String kk[] = k[1].split("\\$");
NumDmgX[0] = kk[1];
}
}
//drawbacks and descriptions
final String DrawBack[] = {"none"};
final String spDesc[] = {"none"};
final String stDesc[] = {"none"};
if (k.length > 2)
{
if (k[2].contains("Drawback$"))
{
String kk[] = k[2].split("\\$");
DrawBack[0] = kk[1];
if (k.length > 3)
spDesc[0] = k[3];
if (k.length > 4)
stDesc[0] = k[4];
}
else
{
if (k.length > 2)
spDesc[0] = k[2];
if (k.length > 3)
stDesc[0] = k[3];
}
}
final SpellAbility DamageTgt = new Spell(card)
{
private static final long serialVersionUID = 7239608350643325111L;
private int damage;
public int getNumDamage()
{
if (NumDmg[0] != -1)
return NumDmg[0];
if (! NumDmgX[0].equals("none"))
return CardFactoryUtil.xCount(card, NumDmgX[0]);
return 0;
}
boolean shouldTgtP()
{
PlayerZone compHand = AllZone.getZone(Constant.Zone.Hand, Constant.Player.Computer);
CardList hand = new CardList(compHand.getCards());
if (hand.size() >= 7) // anti-discard-at-EOT
return true;
if(AllZone.Human_Life.getLife() < (10 - damage)) // if damage from this spell would drop the human to less than 10 life
return true;
return false;
}
Card chooseTgtC()
{
// Combo alert!!
PlayerZone compy = AllZone.getZone(Constant.Zone.Play, Constant.Player.Computer);
CardList cPlay = new CardList(compy.getCards());
if (cPlay.size() > 0)
for (int i = 0; i < cPlay.size(); i++)
if (cPlay.get(i).getName().equals("Stuffy Doll"))
return cPlay.get(i);
PlayerZone human = AllZone.getZone(Constant.Zone.Play, Constant.Player.Human);
CardList hPlay = new CardList(human.getCards());
hPlay = hPlay.filter(new CardListFilter()
{
public boolean addCard(Card c)
{
// will include creatures already dealt damage
return c.isCreature() && ((c.getNetDefense() + c.getDamage()) <= damage) && CardFactoryUtil.canTarget(card, c);
}
}
);
if (hPlay.size() > 0)
{
Card best = hPlay.get(0);
if (hPlay.size() > 1)
{
for (int i = 1; i < hPlay.size(); i++)
{
Card b = hPlay.get(i);
// choose best overall creature?
if (b.getSpellAbility().length > best.getSpellAbility().length ||
b.getKeyword().size() > best.getKeyword().size() ||
b.getNetAttack() > best.getNetAttack())
best = b;
}
}
return best;
}
return null;
}
public boolean canPlayAI()
{
damage = getNumDamage();
if (TgtCP[0] == true)
{
if (shouldTgtP() == true)
{
setTargetPlayer(Constant.Player.Human);
return true;
}
Card c = chooseTgtC();
if (c != null)
{
setTargetCard(c);
return true;
}
}
if (TgtPlayer[0] == true)
{
setTargetPlayer(Constant.Player.Human);
return shouldTgtP();
}
if (TgtCreature[0] == true)
{
Card c = chooseTgtC();
if (c != null)
{
setTargetCard(c);
return c != null;
}
}
return false;
}
public void resolve()
{
damage = getNumDamage();
String tgtP = new String();
if(getTargetCard() != null)
{
if(AllZone.GameAction.isCardInPlay(getTargetCard()) && CardFactoryUtil.canTarget(card, getTargetCard()))
{
Card c = getTargetCard();
//c.addDamage(damage);
AllZone.GameAction.addDamage(c, damage);
tgtP = c.getController();
}
}
else
{
AllZone.GameAction.addDamage(getTargetPlayer(), damage);
tgtP = getTargetPlayer();
}
if (! DrawBack[0].equals("none"))
CardFactoryUtil.doDrawBack(DrawBack[0], damage, card.getController(), AllZone.GameAction.getOpponent(card.getController()), tgtP, card, getTargetCard());
}// resolove
}; //spellAbility
if (NumDmg[0] < 1)
{
if (! spDesc[0].equals("none"))
DamageTgt.setDescription(spDesc[0]);
if (! stDesc[0].equals("none"))
DamageTgt.setStackDescription(stDesc[0]);
}
else
{
DamageTgt.setDescription(card.getName() + " deals " + NumDmg[0] + " damage to target creature or player.");
DamageTgt.setStackDescription(card.getName() +" deals " + NumDmg[0] + " damage.");
}
if (TgtCP[0])
DamageTgt.setBeforePayMana(CardFactoryUtil.input_targetCreaturePlayer(DamageTgt, true));
else if (TgtCreature[0])
DamageTgt.setBeforePayMana(CardFactoryUtil.input_targetCreature(DamageTgt));
else if (TgtPlayer[0])
DamageTgt.setBeforePayMana(CardFactoryUtil.input_targetPlayer(DamageTgt));
card.addSpellAbility(DamageTgt);
}
}// spDamageTgt
- Code: Select all
//parser for non-mana X variables
public static int xCount(Card c, String s)
{
int n = 0;
String cardController = c.getController();
String oppController = AllZone.GameAction.getOpponent(cardController);
PlayerZone myField = AllZone.getZone(Constant.Zone.Play, cardController);
PlayerZone opField = AllZone.getZone(Constant.Zone.Play, oppController);
PlayerZone myYard = AllZone.getZone(Constant.Zone.Graveyard, cardController);
PlayerZone opYard = AllZone.getZone(Constant.Zone.Graveyard, oppController);
PlayerZone myHand = AllZone.getZone(Constant.Zone.Hand, cardController);
PlayerZone opHand = AllZone.getZone(Constant.Zone.Hand, oppController);
final String [] l;
l = s.split("/"); // separate the specification from any math
final String m[] = {"none"};
if (l.length > 1)
m[0] = l[1];
final String [] sq;
sq = l[0].split("\\.");
CardList someCards = new CardList();
//Complex counting methods
// Count$Domain
if (sq[0].contains("Domain"))
{
someCards.addAll(myField.getCards());
String basic[] = {"Forest", "Plains", "Mountain", "Island", "Swamp"};
for(int i = 0; i < basic.length; i++)
if (! someCards.getType(basic[i]).isEmpty())
n++;
return doXMath(n, m);
}
// Count$YourLifeTotal
if (sq[0].contains("YourLifeTotal"))
{
if (cardController.equals(Constant.Player.Computer))
return doXMath(AllZone.Computer_Life.getLife(), m);
else if (cardController.equals(Constant.Player.Human))
return doXMath(AllZone.Human_Life.getLife(), m);
return 0;
}
// Count$OppLifeTotal
if (sq[0].contains("OppLifeTotal"))
{
if (oppController.equals(Constant.Player.Computer))
return doXMath(AllZone.Computer_Life.getLife(), m);
else if (oppController.equals(Constant.Player.Human))
return doXMath(AllZone.Human_Life.getLife(), m);
return 0;
}
// Count$Chroma.<mana letter>
if (sq[0].contains("Chroma"))
return doXMath(getNumberOfManaSymbolsControlledByColor(sq[1], cardController), m);
// Count$Hellbent.<numHB>.<numNotHB>
if (sq[0].contains("Hellbent"))
if (myHand.size() <= 1)
return doXMath(Integer.parseInt(sq[1]), m); // Hellbent
else
return doXMath(Integer.parseInt(sq[2]), m); // not Hellbent
//Generic Zone-based counting
// Count$QualityAndZones.Subquality
// build a list of cards in each possible specified zone
// if a card was ever written to count two different zones,
// make sure they don't get added twice.
boolean MF = false, MY = false, MH = false;
boolean OF = false, OY = false, OH = false;
if (sq[0].contains("YouCtrl"))
if (MF == false)
{
someCards.addAll(myField.getCards());
MF = true;
}
if (sq[0].contains("InYourYard"))
if (MY == false)
{
someCards.addAll(myYard.getCards());
MY = true;
}
if (sq[0].contains("InYourHand"))
if (MH == false)
{
someCards.addAll(myHand.getCards());
MH = true;
}
if (sq[0].contains("OppCtrl"))
if (OF == false)
{
someCards.addAll(opField.getCards());
OF = true;
}
if (sq[0].contains("InOppYard"))
if (OY == false)
{
someCards.addAll(opYard.getCards());
OY = true;
}
if (sq[0].contains("InOppHand"))
if (OH == false)
{
someCards.addAll(opHand.getCards());
OH = true;
}
if (sq[0].contains("OnBattlefield"))
{
if (MF == false)
someCards.addAll(myField.getCards());
if (OF == false)
someCards.addAll(opField.getCards());
}
if (sq[0].contains("InAllYards"))
{
if (MY == false)
someCards.addAll(myYard.getCards());
if (OY = false)
someCards.addAll(opYard.getCards());
}
if (sq[0].contains("InAllHands"))
{
if (MH == false)
someCards.addAll(myHand.getCards());
if (OH == false)
someCards.addAll(opHand.getCards());
}
// filter lists based on the specified quality
// "Clerics you control" - Count$TypeYouCtrl.Cleric
if (sq[0].contains("Type"))
{
someCards = someCards.filter(new CardListFilter()
{
public boolean addCard(Card c)
{
if (c.getType().contains(sq[1]) || c.getKeyword().contains("Changeling"))
return true;
return false;
}
});
}
// "Named <CARDNAME> in all graveyards" - Count$NamedAllYards.<CARDNAME>
if (sq[0].contains("Named"))
{
someCards = someCards.filter(new CardListFilter()
{
public boolean addCard(Card c)
{
if (c.getName().equals(sq[1]))
return true;
return false;
}
});
}
// Refined qualities
// "Untapped Lands" - Count$UntappedTypeYouCtrl.Land
if (sq[0].contains("Untapped"))
{
someCards = someCards.filter(new CardListFilter()
{
public boolean addCard(Card c){
return !c.isTapped();}
});
}
if (sq[0].contains("Tapped"))
{
someCards = someCards.filter(new CardListFilter()
{
public boolean addCard(Card c){
return c.isTapped();}
});
}
// "White Creatures" - Count$WhiteTypeYouCtrl.Creature
if (sq[0].contains("White"))
{
someCards = someCards.filter(new CardListFilter()
{
public boolean addCard(Card c){
return CardUtil.getColor(c) == Constant.Color.White;}
});
}
if (sq[0].contains("Blue"))
{
someCards = someCards.filter(new CardListFilter()
{
public boolean addCard(Card c){
return CardUtil.getColor(c) == Constant.Color.Blue;}
});
}
if (sq[0].contains("Black"))
{
someCards = someCards.filter(new CardListFilter()
{
public boolean addCard(Card c){
return CardUtil.getColor(c) == Constant.Color.Black;}
});
}
if (sq[0].contains("Red"))
{
someCards = someCards.filter(new CardListFilter()
{
public boolean addCard(Card c){
return CardUtil.getColor(c) == Constant.Color.Red;}
});
}
if (sq[0].contains("Green"))
{
someCards = someCards.filter(new CardListFilter()
{
public boolean addCard(Card c){
return CardUtil.getColor(c) == Constant.Color.Green;}
});
}
if (sq[0].contains("Multicolor"))
someCards = someCards.filter(new CardListFilter ()
{
public boolean addCard(Card c){
return (CardUtil.getColors(c).size() > 1);
}
});
if (sq[0].contains("Monocolor"))
someCards = someCards.filter(new CardListFilter ()
{
public boolean addCard(Card c){
return (CardUtil.getColors(c).size() == 1);
}
});
n = someCards.size();
return doXMath(n, m);
}
private static int doXMath(int num, String[] m)
{
if (m[0].equals("none"))
return num;
String[] s = m[0].split("\\.");
if (s[0].contains("Plus"))
return num + Integer.parseInt(s[1]);
else if (s[0].contains("NMinus"))
return Integer.parseInt(s[1]) - num;
else if (s[0].contains("Minus"))
return num - Integer.parseInt(s[1]);
else if (s[0].contains("Twice"))
return num * 2;
else if (s[0].contains("HalfUp"))
return (int) (Math.ceil(num / 2));
else if (s[0].contains("HalfDown"))
return (int) (Math.floor(num / 2));
return num;
}
public static void doDrawBack(String DB, int nDB, String cardController, String Opp, String TgtP, Card Src, Card TgtC)
{
// Drawbacks may be any simple additional effect a spell or ability may have
// not just the negative ones
String d[] = DB.split("/");
int X;
if (d[1].equals("X"))
X = nDB;
else
X = Integer.parseInt(d[1]);
String dbPlayer = new String();
if (d[0].contains("You"))
dbPlayer = cardController;
else if (d[0].contains("Opp"))
dbPlayer = Opp;
else if (d[0].contains("Tgt"))
dbPlayer = TgtP;
if (d[0].contains("Damage"))
AllZone.GameAction.addDamage(dbPlayer, X);
if (d[0].contains("GainLife"))
AllZone.GameAction.addLife(dbPlayer, X);
if (d[0].contains("LoseLife"))
AllZone.GameAction.subLife(TgtP, X);
if (d[0].contains("Discard"))
{
if (d.length > 2)
{
if (d[2].contains("UnlessDiscardType"))
{
String dd[] = d[2].split("\\.");
AllZone.GameAction.discardUnless(dbPlayer, X, dd[1]);
}
if (d[2].contains("AtRandom"))
AllZone.GameAction.discardRandom(dbPlayer, X);
} else
AllZone.GameAction.discard(dbPlayer, X);
}
if (d[0].contains("HandToLibrary"))
AllZone.GameAction.handToLibrary(dbPlayer, X, d[2]);
if (d[0].contains("Draw"))
for (int i=0; i < X; i++)
AllZone.GameAction.drawCard(dbPlayer);
if (d[0].contains("GenToken")) // placeholder for effect
X = X + 0;
if (d[0].contains("ReturnFromYard")) // placeholder for effect
X = X + 0;
if (d[0].contains("Sacrifice")) // placeholder for effect
X = X + 0;
}
- Code: Select all
public void addLife(String player, int life)
{
// place holder for future life gain modification rules
getPlayerLife(player).addLife(life);
}
public void subLife(String player, int life)
{
// place holder for future life loss modification rules
getPlayerLife(player).subtractLife(life);
}
public void addDamage(String player, int damage)
{
// place holder for future damage modification rules (prevention?)
getPlayerLife(player).subtractLife(damage);
}
Commentary
Other keywords can be similarly enhanced by following these bits of code from above:
- Code: Select all
NumDmg[0] = {-1};
if (k[1].length() <= 2) // numeric
NumDmg[0] = Integer.parseInt(k[1]);
else // result of some sort of function
{
if (k[1].startsWith("Count$"))
{
String kk[] = k[1].split("\\$");
NumDmgX[0] = kk[1];
}
}
By the way, the reason we need to use single element arrays (NumDmg[0]) is because the storage for the variable needs to be "final" and yet we need to be able to change the value dynamically.
- Code: Select all
final SpellAbility DamageTgt = new Spell(card)
{
private int damage;
public int getNumDamage()
{
if (NumDmg[0] != -1)
return NumDmg[0];
if (! NumDmgX[0].equals("none"))
return CardFactoryUtil.xCount(card, NumDmgX[0]);
return 0;
}
- Code: Select all
public boolean canPlayAI()
{
damage = getNumDamage();
- Code: Select all
public void resolve()
{
damage = getNumDamage();
At the end of resolve() add a drawback: (basically anytime an effect is tacked on to the primary effect, usually using the word "and" - Lightning Helix) (not two separate effects on one card - Ember Shot)
Note, don't include "Drawback$" in the DB parameter, just the part to the right of the "$".
- Code: Select all
if (! DrawBack[0].equals("none"))
CardFactoryUtil.doDrawBack(DrawBack[0], damage, card.getController(), AllZone.GameAction.getOpponent(card.getController()), tgtP, card, getTargetCard());
- Code: Select all
public static void doDrawBack(String DB, int nDB, String cardController, String Opp, String TgtP, Card Src, Card TgtC)
cardController and Opp are straight forward.
TgtP must be set to either the target player, or the controller of the targeted permanent of the primary effect. ("that creature's controller")
The drawback routine doesn't currently make use of the Src card and TgtC card.
- Code: Select all
if (d[0].contains("GenToken")) // placeholder for effect
X = X + 0;
if (d[0].contains("ReturnFromYard")) // placeholder for effect
X = X + 0;
if (d[0].contains("Sacrifice")) // placeholder for effect
X = X + 0;
If there are other things to count, add them.
But they only have to be added in one place.
I am attaching my changed source files based on the 0828 beta release.
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: spDamageTgt - enhanced damage spell code
by DennisBergkamp » 14 Oct 2009, 16:54
Wow!!! Awesome stuff
I'll merge this and see how it all works.. since this looks pretty complex, it will probably be tricky to find all of the cards that can be added.
Should keep Chris and Sloth busy, no doubt

I'll merge this and see how it all works.. since this looks pretty complex, it will probably be tricky to find all of the cards that can be added.
Should keep Chris and Sloth busy, no doubt

-
DennisBergkamp - AI Programmer
- Posts: 2602
- Joined: 09 Sep 2008, 15:46
- Has thanked: 0 time
- Been thanked: 0 time
Re: spDamageTgt - enhanced damage spell code
by DennisBergkamp » 14 Oct 2009, 16:58
In fact, some of these keywords are so complex, it might be easier to just program them
By the way, I just noticed Feast of Flesh, does your code take into account the current order (graveyard, then resolve) or the new one (resolve, then grave) ?

By the way, I just noticed Feast of Flesh, does your code take into account the current order (graveyard, then resolve) or the new one (resolve, then grave) ?
-
DennisBergkamp - AI Programmer
- Posts: 2602
- Joined: 09 Sep 2008, 15:46
- Has thanked: 0 time
- Been thanked: 0 time
Re: spDamageTgt - enhanced damage spell code
by Sloth » 14 Oct 2009, 20:05
Wow Rob! This keyword is really flexible. Before I will post dozens of untested cards again (I'm unable to compile the source), I will wait for the next beta to try using the keyword:
But I can't help but ask: would this code for Blightning be correct?
PS: Your Feast of Flesh is missing the Sorcery Type.
PPS: Is it possible to use the drawbacks of a spell without targeted damage?
But I can't help but ask: would this code for Blightning be correct?
- Code: Select all
Blightning
1 B R
Sorcery
no text
spDamageTgtP:3:Drawback$TgtDiscard/2:Blightning deals 3 damage to target player. That player discards two cards.:Blightning - deals 3 damage to target player and that player discards two cards.
PS: Your Feast of Flesh is missing the Sorcery Type.
PPS: Is it possible to use the drawbacks of a spell without targeted damage?
Last edited by Sloth on 15 Oct 2009, 12:45, edited 1 time in total.
-
Sloth - Programmer
- Posts: 3498
- Joined: 23 Jun 2009, 19:40
- Has thanked: 125 times
- Been thanked: 507 times
Re: spDamageTgt - enhanced damage spell code
by Rob Cashwalker » 14 Oct 2009, 21:02
except for missing "no text" I think your Blightning looks right. The stack description is optional, really. I think the game uses "CARDNAME targets cardname", by default, which is good enough. There weren't many stack descriptions in the explicitly coded cards that I looked at for reference.
I'm assuming that we fix the order of resolve and graveyard somehow. In my final tests I just reversed the order because I wasn't making use of any buyback cards in the test decks.
The concept behind the Drawback and Count methods is that they can be integrated into other keywords without much trouble, or even executed directly from explicit card code.
No, I wouldn't recommend using the keyword as-is with zero damage, just to make use of the drawback engine.
BTW, As I finished up the post this morning I recalled another parameter that I'd like to support - the targeting restriction system that I made up for spDestroyTgt. This goes hand-in-hand with the idea that position-based parameters complicate things when there are a variable number of them.
I'm assuming that we fix the order of resolve and graveyard somehow. In my final tests I just reversed the order because I wasn't making use of any buyback cards in the test decks.
The concept behind the Drawback and Count methods is that they can be integrated into other keywords without much trouble, or even executed directly from explicit card code.
No, I wouldn't recommend using the keyword as-is with zero damage, just to make use of the drawback engine.
BTW, As I finished up the post this morning I recalled another parameter that I'd like to support - the targeting restriction system that I made up for spDestroyTgt. This goes hand-in-hand with the idea that position-based parameters complicate things when there are a variable number of them.
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: spDamageTgt - enhanced damage spell code
by Sloth » 16 Oct 2009, 14:50
I tried First Volley (your example, Rob) and Blightning (my proposition) in the new version and they work as intendet, but their description is just "First Volley deals 1 damage to target creature or player." and "Blightning deals 3 damage to target creature or player." (even though First Volley can only target creatures and blightning only players) instead of the description given in cards.txt.
Maybe something is wrong with the syntax?
Edit: I forgot to mention, that blighning produced an error message when hitting the computer with just one card in hand.
Maybe something is wrong with the syntax?
Edit: I forgot to mention, that blighning produced an error message when hitting the computer with just one card in hand.
-
Sloth - Programmer
- Posts: 3498
- Joined: 23 Jun 2009, 19:40
- Has thanked: 125 times
- Been thanked: 507 times
Re: spDamageTgt - enhanced damage spell code
by Rob Cashwalker » 16 Oct 2009, 16:13
Yeah, you're right. This part needs to be changed:
Change all the code from above to this:
- Code: Select all
if (NumDmg[0] < 1)
{
if (! spDesc[0].equals("none"))
DamageTgt.setDescription(spDesc[0]);
if (! stDesc[0].equals("none"))
DamageTgt.setStackDescription(stDesc[0]);
}
else
{
DamageTgt.setDescription(card.getName() + " deals " + NumDmg[0] + " damage to target creature or player.");
DamageTgt.setStackDescription(card.getName() +" deals " + NumDmg[0] + " damage.");
}
Change all the code from above to this:
- Code: Select all
if (! spDesc[0].equals("none"))
DamageTgt.setDescription(spDesc[0]);
else
{
String s;
s = card.getName() + " deals " + NumDmg[0] + " damage to target";
if (TgtCP[0])
s = s + " creature or player.";
else if (TgtCreature[0])
s = s + " creature.";
else if (TgtPlayer[0])
s = s + " player.";
DamageTgt.setDescription(s);
}
if (! stDesc[0].equals("none"))
DamageTgt.setStackDescription(stDesc[0]);
else
DamageTgt.setStackDescription(card.getName() + " - deals " + NumDmg[0] + " damage.");
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: spDamageTgt - enhanced damage spell code
by Sloth » 18 Oct 2009, 12:54
I tested First Volley and Blightning in the Version 1018 and they work fine. I try to test some more cards with the enhanced keyword the next days.
-
Sloth - Programmer
- Posts: 3498
- Joined: 23 Jun 2009, 19:40
- Has thanked: 125 times
- Been thanked: 507 times
Re: spDamageTgt - enhanced damage spell code
by Chris H. » 18 Oct 2009, 14:45
`Sloth wrote:I tested First Volley and Blightning in the Version 1018 and they work fine. I try to test some more cards with the enhanced keyword the next days.
Thank you.

-
Chris H. - Forge Moderator
- Posts: 6320
- Joined: 04 Nov 2008, 12:11
- Location: Mac OS X Yosemite
- Has thanked: 644 times
- Been thanked: 643 times
Re: spDamageTgt - enhanced damage spell code
by Sloth » 18 Oct 2009, 16:59
I started searching for cards to add alphabetically. Here are my first results:
and the entries in card-pictures.txt:
- Code: Select all
Orcish Cannonade
1 R R
Instant
no text
spDamageTgtCP:2:Drawback$DamageYou/3:Orcish Cannonade deals 2 damage to target creature or player and 3 damage to you. Draw a card.
Cantrip
Kindle
1 R
Instant
no text
spDamageTgtCP:Count$NamedInAllYards.Kindle/Plus.2:Kindle deals X damage to target creature or player, where X is 2 plus the number of cards named Kindle in all graveyards.
Ire of Kaminari
3 R
Instant — Arcane
no text
spDamageTgtCP:Count$TypeInYourYard.Arcane:Ire of Kaminari deals damage to target creature or player equal to the number of Arcane cards in your graveyard.
Goblin War Strike
R
Sorcery
no text
spDamageTgtP:Count$TypeYouCtrl.Goblin:Goblin War Strike deals damage equal to the number of Goblins you control to target player.
Flame Burst
1 R
Instant
no text
spDamageTgtCP:Count$NamedInAllYards.Flame Burst/Plus.2:Flame Burst deals X damage to target creature or player, where X is 2 plus the number of cards named Flame Burst in all graveyards.
Feedback Bolt
4 R
Instant
no text
spDamageTgtP:Count$TypeYouCtrl.Artifact:Feedback Bolt deals damage to target player equal to the number of artifacts you control.:Feedback Bolt - deals damage to target player equal to the number of artifacts you control.
Blightning
1 B R
Sorcery
no text
spDamageTgtP:3:Drawback$TgtDiscard/2:Blightning deals 3 damage to target player. That player discards two cards.:Blightning - deals 3 damage and that player discards two cards.
and the entries in card-pictures.txt:
- Code: Select all
orcish_cannonade.jpg http://www.wizards.com/global/images/magic/general/orcish_cannonade.jpg
kindle.jpg http://www.wizards.com/global/images/magic/general/kindle.jpg
ire_of_kaminari.jpg http://www.wizards.com/global/images/magic/general/ire_of_kaminari.jpg
goblin_war_strike.jpg http://www.wizards.com/global/images/magic/general/goblin_war_strike.jpg
flame_burst.jpg http://www.wizards.com/global/images/magic/general/flame_burst.jpg
feedback_bolt.jpg http://www.wizards.com/global/images/magic/general/feedback_bolt.jpg
blightning.jpg http://www.wizards.com/global/images/magic/general/blightning.jpg
Last edited by Sloth on 19 Oct 2009, 13:52, edited 1 time in total.
-
Sloth - Programmer
- Posts: 3498
- Joined: 23 Jun 2009, 19:40
- Has thanked: 125 times
- Been thanked: 507 times
Re: spDamageTgt - enhanced damage spell code
by Marek14 » 18 Oct 2009, 17:32
I'd suggest to change Flame Burst a bit so it would also count cards named "Pardic Firecat" - that could be added since apart from the Flame Burst clause it's just a 2/3 haste.Sloth wrote:I started searching for cards to add alphabetically. Here are my first results:I tested them and found no problems.
- Code: Select all
Orcish Cannonade
1 R R
Instant
no text
spDamageTgtCP:2:Drawback$DamageYou/3:Orcish Cannonade deals 2 damage to target creature or player and 3 damage to you.
Kindle
1 R
Instant
no text
spDamageTgtCP:Count$NamedInAllYards.Kindle/Plus.2:Kindle deals X damage to target creature or player, where X is 2 plus the number of cards named Kindle in all graveyards.
Ire of Kaminari
3 R
Instant — Arcane
no text
spDamageTgtCP:Count$TypeInYourYard.Arcane:Ire of Kaminari deals damage to target creature or player equal to the number of Arcane cards in your graveyard.
Goblin War Strike
R
Sorcery
no text
spDamageTgtP:Count$TypeYouCtrl.Goblin:Goblin War Strike deals damage equal to the number of Goblins you control to target player.
Flame Burst
1 R
Instant
no text
spDamageTgtCP:Count$NamedInAllYards.Flame Burst/Plus.2:Flame Burst deals X damage to target creature or player, where X is 2 plus the number of cards named Flame Burst in all graveyards.
Feedback Bolt
4 R
Instant
no text
spDamageTgtP:Count$TypeYouCtrl.Artifact:Feedback Bolt deals damage to target player equal to the number of artifacts you control.:Feedback Bolt - deals damage to target player equal to the number of artifacts you control.
Blightning
1 B R
Sorcery
no text
spDamageTgtP:3:Drawback$TgtDiscard/2:Blightning deals 3 damage to target player. That player discards two cards.:Blightning - deals 3 damage and that player discards two cards.
and the entries in card-pictures.txt:
- Code: Select all
orcish_cannonade.jpg http://www.wizards.com/global/images/magic/general/orcish_cannonade.jpg
kindle.jpg http://www.wizards.com/global/images/magic/general/kindle.jpg
ire_of_kaminari.jpg http://www.wizards.com/global/images/magic/general/ire_of_kaminari.jpg
goblin_war_strike.jpg http://www.wizards.com/global/images/magic/general/goblin_war_strike.jpg
flame_burst.jpg http://www.wizards.com/global/images/magic/general/flame_burst.jpg
feedback_bolt.jpg http://www.wizards.com/global/images/magic/general/feedback_bolt.jpg
blightning.jpg http://www.wizards.com/global/images/magic/general/blightning.jpg
This would work fine for now. Something different would be needed if MTG Forge will support Yixlid Jailer one day.
Re: spDamageTgt - enhanced damage spell code
by Rob Cashwalker » 19 Oct 2009, 02:53
Orcish Cannonade needs Cantrip and a text line for "Draw a card."
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
Re: spDamageTgt - enhanced damage spell code
by Sloth » 19 Oct 2009, 13:53
Fixed!Rob Cashwalker wrote:Orcish Cannonade needs Cantrip and a text line for "Draw a card."
-
Sloth - Programmer
- Posts: 3498
- Joined: 23 Jun 2009, 19:40
- Has thanked: 125 times
- Been thanked: 507 times
Re: spDamageTgt - enhanced damage spell code
by Sloth » 20 Oct 2009, 15:04
Finished going through the cards. Here is the rest:
- Code: Select all
Vicious Hunger
B B
Sorcery
no text
spDamageTgtC:2:Drawback$GainLifeYou/2:Vicious Hunger deals 2 damage to target creature and you gain 2 life.
Vampiric Feast
5 B B
Sorcery
no text
spDamageTgtCP:4:Drawback$GainLifeYou/4:Vampiric Feast deals 4 damage to target creature or player and you gain 4 life.
Thermal Blast
4 R
Instant
no text
spDamageTgtC:Count$Hellbent.5.3:Thermal Blast deals 3 damage to target creature. Threshold — Thermal Blast deals 5 damage to that creature instead if seven or more cards are in your graveyard.
Spitting Earth
1 R
Sorcery
no text
spDamageTgtC:Count$TypeYouCtrl.Mountain:Spitting Earth deals damage equal to the number of Mountains you control to target creature.
Spire Barrage
4 R
Sorcery
no text
spDamageTgtCP:Count$TypeYouCtrl.Mountain:Spire Barrage deals damage to target creature or player equal to the number of Mountains you control.
Spiraling Embers
3 R
Sorcery Arcane
no text
spDamageTgtCP:Count$InYourHand:Spiraling Embers deals damage to target creature or player equal to the number of cards in your hand.
Skred
R
Instant
no text
spDamageTgtC:Count$TypeYouCtrl.Snow:Skred deals damage to target creature equal to the number of snow permanents you control.
Seismic Strike
2 R
Instant
no text
spDamageTgtC:Count$TypeYouCtrl.Mountain:Seismic Strike deals damage to target creature equal to the number of Mountains you control.
Rockslide Ambush
1 R
Sorcery
no text
spDamageTgtC:Count$TypeYouCtrl.Mountain:Rockslide Ambush deals damage equal to the number of Mountains you control to target creature.
Profane Prayers
2 B B
Sorcery
no text
spDamageTgtCP:Count$TypeYouCtrl.Cleric:Drawback$GainLifeYou/X:Profane Prayers deals X damage to target creature or player and you gain X life, where X is the number of Clerics on the battlefield.
- Code: Select all
vicious_hunger.jpg http://www.wizards.com/global/images/magic/general/vicious_hunger.jpg
vampiric_feast.jpg http://resources.wizards.com/magic/cards/po/en-us/card4243.jpg
thermal_blast.jpg http://www.wizards.com/global/images/magic/general/thermal_blast.jpg
spitting_earth.jpg http://www.wizards.com/global/images/magic/general/spitting_earth.jpg
spire_barrage.jpg http://www.wizards.com/global/images/magic/general/spire_barrage.jpg
spiraling_embers.jpg http://www.wizards.com/global/images/magic/general/spiraling_embers.jpg
skred.jpg http://www.wizards.com/global/images/magic/general/skred.jpg
seismic_strike.jpg http://www.wizards.com/global/images/magic/general/seismic_strike.jpg
rockslide_ambush.jpg http://www.wizards.com/global/images/magic/general/rockslide_ambush.jpg
profane_prayers.jpg http://www.wizards.com/global/images/magic/general/profane_prayers.jpg
-
Sloth - Programmer
- Posts: 3498
- Joined: 23 Jun 2009, 19:40
- Has thanked: 125 times
- Been thanked: 507 times
Re: spDamageTgt - enhanced damage spell code
by Rob Cashwalker » 20 Oct 2009, 17:48
But the great part of the structure is that we don't necessarily need to fully support Snow Mana (though I just saw the title of your post before clicking in to this one) or Splice onto Arcane in order for them to be somewhat relevant at some level.
The Force will be with you, Always.
-
Rob Cashwalker - Programmer
- Posts: 2167
- Joined: 09 Sep 2008, 15:09
- Location: New York
- Has thanked: 5 times
- Been thanked: 40 times
29 posts
• Page 1 of 2 • 1, 2
Who is online
Users browsing this forum: No registered users and 32 guests