It is currently 03 Sep 2025, 07:35
   
Text Size

spDamageTgt - enhanced damage spell code

Post MTG Forge Related Programming Questions Here

Moderators: timmermac, Blacksmith, KrazyTheFox, Agetian, friarsol, CCGHQ Admins

spDamageTgt - enhanced damage spell code

Postby 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:
Code: Select all
spDamageTgt:#Dmg
spDamageTgt:#Dmg:Drawback
spDamageTgt:#Dmg:SpellDescription:StackDescription
spDamageTgt:#Dmg:Drawback:SpellDescription:StackDescription
#Dmg may be a number, (like 1, 2, 3...) or it may be the result of a function, of which there's only one right now, Count$.
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
Some Examples of Count$ parameters:
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
Examples of Drawback$:

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.:Psionic 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.:Douse 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
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
CardFactoryUtil.java
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;
  }
GameActionUtil.java
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];
             }
          }
Whatever number an effect is looking for, check if it's a number or a "function" result.
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;
             }
Create or modify a function that either returns the actual numeric value or executes the count function.
Code: Select all
             public boolean canPlayAI()
             {
                damage = getNumDamage();
Code: Select all
             public void resolve()
             {
                damage = getNumDamage();
Call the function before processing anything else.

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)
DB is the string with the drawback specified. nDB is used if DB contains X as a number.
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;
Notice, the code's not finished. If there are other effects missing, add them.
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.
User avatar
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

Postby DennisBergkamp » 14 Oct 2009, 16:54

Wow!!! Awesome stuff =D>

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 :mrgreen:
User avatar
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

Postby DennisBergkamp » 14 Oct 2009, 16:58

In fact, some of these keywords are so complex, it might be easier to just program them :lol:

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) ?
User avatar
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

Postby 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?

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.
I'm not sure about the stack description. What would you suggest?

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.
User avatar
Sloth
Programmer
 
Posts: 3498
Joined: 23 Jun 2009, 19:40
Has thanked: 125 times
Been thanked: 507 times

Re: spDamageTgt - enhanced damage spell code

Postby 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.
The Force will be with you, Always.
User avatar
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

Postby 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.
User avatar
Sloth
Programmer
 
Posts: 3498
Joined: 23 Jun 2009, 19:40
Has thanked: 125 times
Been thanked: 507 times

Re: spDamageTgt - enhanced damage spell code

Postby Rob Cashwalker » 16 Oct 2009, 16:13

Yeah, you're right. This part needs to be changed:
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.");
          }
What I was trying to do is use the generated descriptions when the damage was a specific amount. But then as I was typing out the documentation, I decided it would be better to write them out manually.
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.");
Now the descriptions are optional if missing, then they're generated. If the NumDmg[0] is -1 because it's an X value, then too bad, the description will be wrong. If there's a drawback, then that won't be printed either.
The Force will be with you, Always.
User avatar
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

Postby 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.
User avatar
Sloth
Programmer
 
Posts: 3498
Joined: 23 Jun 2009, 19:40
Has thanked: 125 times
Been thanked: 507 times

Re: spDamageTgt - enhanced damage spell code

Postby 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. :)
User avatar
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

Postby Sloth » 18 Oct 2009, 16:59

I started searching for cards to add alphabetically. Here are my first results:

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.
I tested them and found no problems.

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
Edit: Fixed Orcish Cannonade
Last edited by Sloth on 19 Oct 2009, 13:52, edited 1 time in total.
User avatar
Sloth
Programmer
 
Posts: 3498
Joined: 23 Jun 2009, 19:40
Has thanked: 125 times
Been thanked: 507 times

Re: spDamageTgt - enhanced damage spell code

Postby Marek14 » 18 Oct 2009, 17:32

Sloth wrote:I started searching for cards to add alphabetically. Here are my first results:

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.
I tested them and found no problems.

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
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.

This would work fine for now. Something different would be needed if MTG Forge will support Yixlid Jailer one day.
Marek14
Tester
 
Posts: 2773
Joined: 07 Jun 2008, 07:54
Has thanked: 0 time
Been thanked: 303 times

Re: spDamageTgt - enhanced damage spell code

Postby 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.
User avatar
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

Postby Sloth » 19 Oct 2009, 13:53

Rob Cashwalker wrote:Orcish Cannonade needs Cantrip and a text line for "Draw a card."
Fixed!
User avatar
Sloth
Programmer
 
Posts: 3498
Joined: 23 Jun 2009, 19:40
Has thanked: 125 times
Been thanked: 507 times

Re: spDamageTgt - enhanced damage spell code

Postby 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.
card-pictures.txt:
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
The problem with Skred and Ire of Kaminari is, that they are pretty useless in a generated deck, so it's better to make a remove entry in GenerateConstructedDeck.java.
User avatar
Sloth
Programmer
 
Posts: 3498
Joined: 23 Jun 2009, 19:40
Has thanked: 125 times
Been thanked: 507 times

Re: spDamageTgt - enhanced damage spell code

Postby 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.
User avatar
Rob Cashwalker
Programmer
 
Posts: 2167
Joined: 09 Sep 2008, 15:09
Location: New York
Has thanked: 5 times
Been thanked: 40 times

Next

Return to Developer's Corner

Who is online

Users browsing this forum: No registered users and 32 guests

Main Menu

User Menu

Our Partners


Who is online

In total there are 32 users online :: 0 registered, 0 hidden and 32 guests (based on users active over the past 10 minutes)
Most users ever online was 7303 on 15 Jul 2025, 20:46

Users browsing this forum: No registered users and 32 guests

Login Form