It is currently 13 Sep 2025, 17:04
   
Text Size

Programming a card

Post MTG Forge Related Programming Questions Here

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

Re: Programming a card

Postby Triadasoul » 07 Dec 2009, 19:18

I wanted to add Transmute keyword (it's very similar to cycling). Where else except CardFactoryUtil should i add a code ?


Code: Select all
  public static SpellAbility ability_transmute(final Card sourceCard, final String transmuteCost)
  {
    final SpellAbility transmute = new Ability_Hand(sourceCard, transmuteCost)
    {
     private static final long serialVersionUID = -4960704261761785512L;

     public boolean canPlayAI() {return false;}
     public boolean canPlay()
     {
     if ((AllZone.Phase.getPhase().equals(Constant.Phase.Main2)&& AllZone.Phase.getActivePlayer() == sourceCard.getController()) || (AllZone.Phase.getPhase().equals(Constant.Phase.Main1) && AllZone.Phase.getActivePlayer() == sourceCard.getController()) )
        return true;
     else 
          return false;
     }
    

      public void resolve()
      {
         PlayerZone lib = AllZone.getZone(Constant.Zone.Library, sourceCard.getController());
          PlayerZone hand = AllZone.getZone(Constant.Zone.Hand, sourceCard.getController());
                 
        
          CardList cards = new CardList(lib.getCards());
          CardList sameCost = new CardList();
         
          for (int i=0;i<cards.size();i++)
          {
             if( CardUtil.getConvertedManaCost(cards.get(i).getManaCost()) == CardUtil.getConvertedManaCost(sourceCard.getManaCost()) ) 
             {
                sameCost.add(cards.get(i));
             }
          }

         
          if(sameCost.size() == 0)
            return;

         
            Object o = AllZone.Display.getChoiceOptional("Select a card", sameCost.toArray());
            if(o != null)
            {
              //ability.setTargetCard((Card)o);
              //AllZone.Stack.add(ability);
              Card c1 = (Card)o;
              lib.remove(c1);
              hand.add(c1);
                         
                           
            }
            AllZone.GameAction.shuffle(sourceCard.getController());
          }
     
    };
    transmute.setDescription("Transmute "  +transmuteCost +" (" +transmuteCost +", Discard this card: Search your library for a card with the same converted mana cost as the discarded card, reveal that card, and put it into your hand. Then shuffle your library. Play this ability only any time you could play a sorcery.)");
    transmute.setStackDescription(sourceCard +" Transmute: Search your library for a card with the same converted mana cost.");
    return transmute;
  }//ability_transmute()
Triadasoul
 
Posts: 223
Joined: 21 Jun 2008, 20:17
Has thanked: 0 time
Been thanked: 4 times

Re: Programming a card

Postby mtgrares » 07 Dec 2009, 20:06

I briefly looked over the code. Your SpellAbility.canPlay() doesn't check to see if the card is in your hand. The easiest way to do that is to call super.canPlay() which calls Ability_Hand.canPlay() which just verifies that the card is in your hand.

And since I'm a perfectionist, here is the code from Ability_Hand.canPlay(). (The Serializable stuff can be removed. I used to write stuff to the hard drive as a primitive undo function.)

Code: Select all
abstract public class Ability_Hand extends SpellAbility implements java.io.Serializable
{
    public boolean canPlay()
    {
      PlayerZone zone = AllZone.getZone(getSourceCard());
      return zone.is(Constant.Zone.Hand, getSourceCard().getController());
    }
    etc... other methods
}
mtgrares
DEVELOPER
 
Posts: 1352
Joined: 08 Sep 2008, 22:10
Has thanked: 3 times
Been thanked: 12 times

Re: Programming a card

Postby DennisBergkamp » 07 Dec 2009, 20:54

Ah yes, it looks like you should add && super.canPlay() into canPlay().

I think you also need to actually add the ability in CardFactory and CardFactory_Creatures to the card, from a keyword I assume. The code will be very close to the cycling code, I would also add it right after that:

Code: Select all
if (hasKeyword(card, "Cycling") != -1)
    {
      int n = hasKeyword(card, "Cycling");
      if (n != -1)
      {
        String parse = card.getKeyword().get(n).toString();
        card.removeIntrinsicKeyword(parse);

        String k[] = parse.split(":");
        final String manacost = k[1];

        card.addSpellAbility(CardFactoryUtil.ability_cycle(card, manacost));
      }
    }//Cycling
Just replace the instances of "Cyling" with "Transmute" and make it add your TransmuteAbility in CardFactoryUtil.

By the way, I had another thought about Dovescape.... it seems if you add it at the top of executePlayCardEffects(), like so:

Code: Select all
public static void executePlayCardEffects(SpellAbility sa)
   {
      // experimental:
      // this method check for cards that have triggered abilities whenever a
      // card gets played
      // (called in MagicStack.java)
      Card c = sa.getSourceCard();
      
      playCard_Dovescape(c); //keep this one top
      playCard_Emberstrike_Duo(c);
      playCard_Gravelgill_Duo(c);
                .... etc.
It shouldn't counter other "PlayCard" effects anymore. Then again, originally it was added to the stack before Fable of Wolf and Owl, so I'm not sure how it would counter that ability in the first place.
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: Programming a card

Postby Marek14 » 07 Dec 2009, 22:01

If you're doing Transmute, how about landcycling? Generalized XXXcycling code would allow for Scourge landcyclers (Chartooth Cougar, Elvish Aberration, Eternal Dragon, Noble Templar, Shoreline Ranger, Twisted Abomination and Wirewood Guardian), Future Sight cyclers (Homing Sliver [that would be harder] and Vedalken AEthermage), Conflux basic landcyclers (Absorb Vis, Fiery Fall, Gleam of Resistance, Sylvan Bounty and Traumatic Visions), and Alara Reborn double landcyclers (Igneous Pouncer, Jhessian Zombies, Pale Recluse, Sanctum Plowbeast and Valley Rannet)
Marek14
Tester
 
Posts: 2773
Joined: 07 Jun 2008, 07:54
Has thanked: 0 time
Been thanked: 303 times

Re: Programming a card

Postby Triadasoul » 08 Dec 2009, 06:49

Thank you for your advises and feedback :D ! I've just added transmute keyword, and found that cycling and transmute doesn't work for lands. What code should i add to CardFactory_Lands? Simply copying code for cycling/transmute from Cardfactory_Creatures doesn't work here.

Also i had a problem coding Honden of Seeing Winds within spell ability. For example Honden of Cleansing Fire works fine within ability:
Code: Select all
   private static void upkeep_Honden_of_Cleansing_Fire()
   {
      final String player = AllZone.Phase.getActivePlayer();
      PlayerZone play = AllZone.getZone(Constant.Zone.Play, player);

      CardList list = new CardList();
      list.addAll(play.getCards());

      list = list.getName("Honden of Cleansing Fire");

      for (int i = 0; i < list.size(); i++) {
         final Ability ability = new Ability(list.get(i), "0")
          {   
         public void resolve() {
         PlayerZone Play = AllZone.getZone(Constant.Zone.Play, player);
         CardList hondlist = new CardList();
         hondlist.addAll(Play.getCards());
         hondlist = hondlist.getType("Shrine");
           for (int j = 0; j < hondlist.size() ; j++)
          {       
             AllZone.GameAction.getPlayerLife(player).addLife(2);
          }}
          };
          ability.setStackDescription(list.get(i)+" - " + list.get(i).getController() + " gains 2 life for each Shrine he controls.");
          AllZone.Stack.add(ability);   
      }
                  
   }// upkeep_Honden_of_Cleansing_Fire

and Honden of Seeing Winds doesn't. The only difference between them is "AllZone.GameAction.drawCard(player);" this string. Is there some rules of using it ?
Code: Select all
private static void upkeep_Honden_of_Seeing_Winds()
   {
      final String player = AllZone.Phase.getActivePlayer();
      PlayerZone play = AllZone.getZone(Constant.Zone.Play, player);

      CardList list = new CardList();
      list.addAll(play.getCards());

      list = list.getName("Honden of Seeing Winds");

      for (int i = 0; i < list.size(); i++) {
         final Ability ability2 = new Ability(list.get(i), "0")
          {   
         public void resolve() {
         PlayerZone Play = AllZone.getZone(Constant.Zone.Play, player);
         CardList hondlist = new CardList();
         hondlist.addAll(Play.getCards());
         hondlist = hondlist.getType("Shrine");
           for (int j = 0; j < hondlist.size() ; j++)
          {       
              AllZone.GameAction.drawCard(player);
          }}
          };
          ability2.setStackDescription(list.get(i)+" - " + list.get(i).getController() + " draws a card for each Shrine he controls.");
        AllZone.Stack.add(ability2);   
      }
                  
   }// upkeep_Honden_of_Seeing_Winds
But it works fine this way
Code: Select all
private static void upkeep_Honden_of_Seeing_Winds()
   {
      final String player = AllZone.Phase.getActivePlayer();
      PlayerZone play = AllZone.getZone(Constant.Zone.Play, player);

      CardList list = new CardList();
      list.addAll(play.getCards());

      list = list.getName("Honden of Seeing Winds");

      for (int i = 0; i < list.size(); i++) {
//         final Ability ability2 = new Ability(list.get(i), "0")
   //       {   
      //   public void resolve() {
         PlayerZone Play = AllZone.getZone(Constant.Zone.Play, player);
         CardList hondlist = new CardList();
         hondlist.addAll(Play.getCards());
         hondlist = hondlist.getType("Shrine");
           for (int j = 0; j < hondlist.size() ; j++)
          {       
              AllZone.GameAction.drawCard(player);
          }//}
        //  };
      //    ability2.setStackDescription(list.get(i)+" - " + list.get(i).getController() + " draws a card for each Shrine he controls.");
   //       AllZone.Stack.add(ability2);   
      }
                  
   }// upkeep_Honden_of_Seeing_Winds
Update: Certain type cycling coded. But i have a question: what should i change to add 2 or more instances of one keyword to a card?
Triadasoul
 
Posts: 223
Joined: 21 Jun 2008, 20:17
Has thanked: 0 time
Been thanked: 4 times

Re: Programming a card

Postby zerker2000 » 08 Dec 2009, 08:11

Yes, apparently right now the draw step isn't separate from upkeep, and any attempt to draw during it will give you that turn's draw and nothing else. For multiple cycling, make the if a while
O forest, hold thy wand'ring son
Though fears assail the door.
O foliage, cloak thy ravaged one
In vestments cut for war.


--Eladamri, the Seed of Freyalise
zerker2000
Programmer
 
Posts: 569
Joined: 09 May 2009, 21:40
Location: South Pasadena, CA
Has thanked: 0 time
Been thanked: 0 time

Re: Programming a card

Postby Triadasoul » 08 Dec 2009, 08:24

For typecycling it works. Thank you =)

And about Honden i use AllZone.GameAction.drawCard(player); in one case in the stack and in other out of it. In second case it works.
Triadasoul
 
Posts: 223
Joined: 21 Jun 2008, 20:17
Has thanked: 0 time
Been thanked: 4 times

Re: Programming a card

Postby Triadasoul » 08 Dec 2009, 11:53

And another question about Leechridden Swamp. Ai can simultaneously tap it for ability and pay for it with tapping the same land for mana. Is it a bug of my coding or it's the ai payment bug?

Code: Select all
//*************** START *********** START **************************
          else if(cardName.equals("Leechridden Swamp"))
          {
            //tap sacrifice
            final Ability_Tap ability = new Ability_Tap(card, "B")
            {
              private static final long serialVersionUID = 5441640362881917927L;

               public boolean canPlay()
              {
               CardList play = new CardList();
            if(card.getController().equals(Constant.Player.Human))
               play.addAll(AllZone.Human_Play.getCards());
            else
               play.addAll(AllZone.Computer_Play.getCards());
            play = play.filter(new CardListFilter(){
                    public boolean addCard(Card c) {
                       return (CardUtil.getColors(c).contains(Constant.Color.Black));
                    }
                 });
            if ( play.size()>=2 )
               return true;
            else return false;
              }//canPlay()               
               
               public void resolve()
              {
                 AllZone.GameAction.getPlayerLife(AllZone.GameAction.getOpponent(card.getController())).subtractLife(1);
              }

            };

            card.addSpellAbility(ability);
            ability.setStackDescription(card + " - Each opponent loses 1 life.");
            ability.setDescription("tap, B: Each opponent loses 1 life. Activate this ability only if you control two or more black permanents.");

          }//*************** END ************ END **************************
Triadasoul
 
Posts: 223
Joined: 21 Jun 2008, 20:17
Has thanked: 0 time
Been thanked: 4 times

Re: Programming a card

Postby Chris H. » 08 Dec 2009, 12:23

Triadasoul wrote:I've just added transmute keyword, and found that cycling and transmute doesn't work for lands.
`
Thank you for the work that you have submitted. The next beta release will be one of our biggest and best. :D

Someone noticed earlier this year that they could cycle a land during the computer's turn. Our code base has changed and it appears that we can no longer cycle lands during the computer's turn.

I will try to remember to add a NOTE to these lands and merge this into our SVN.
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: Programming a card

Postby Marek14 » 10 Dec 2009, 14:36

Early Worldwake card:

Leatherback Baloth
G G G
Creature Beast
no text
4/5

Of course, picture has to be supplied by hand...

http://forums.mtgsalvation.com/showthread.php?t=204435

There's another full card known (with a picture), but that's probably for you to implement. It can be probably done by slight modification of, say, Faerie Conclave.

http://forums.mtgsalvation.com/showthread.php?t=204793
Marek14
Tester
 
Posts: 2773
Joined: 07 Jun 2008, 07:54
Has thanked: 0 time
Been thanked: 303 times

Re: Programming a card

Postby Triadasoul » 10 Dec 2009, 21:24

I want to add a soulshift keyword, but i don't know where should i add code for it, the main idea was to put it into gameactionutil in "destroy creature part" something like this:

Code: Select all
   public static int shouldSoulshift(Card c) {
      ArrayList<String> a = c.getKeyword();
      for (int i = 0; i < a.size(); i++)
         if (a.get(i).toString().startsWith("Soulshift"))
      return i;
      return -1;
   }
   public static void executeDestroyCreatureCardEffects(Card c, Card destroyed)
   {
                      if (c.getName().equals("Goblin Sharpshooter"))
            destroyCreature_Goblin_Sharpshooter(c, destroyed);

    while (shouldSoulshift(destroyed) != -1)
            {
              int n = shouldSoulshift((destroyed);
              if (n != -1)
              {
                String parse = destroyed.getKeyword().get(n).toString();
                destroyed.removeIntrinsicKeyword(parse);
      
                String k[] = parse.split(":");
                final String manacost = k[1];
               
      
                AllZone.Stack.add((CardFactoryUtil.ability_soulshift(destroyed, manacost));
Is it right to add it there or should i add it somewhere else?
Triadasoul
 
Posts: 223
Joined: 21 Jun 2008, 20:17
Has thanked: 0 time
Been thanked: 4 times

Re: Programming a card

Postby DennisBergkamp » 11 Dec 2009, 02:37

Yes, that's exactly where it should be put. Note though, that currently cards that trigger things on leaving play are a bit buggy, because mass destroy effects (WoG, Tranquility, ...) in Forge destroy things one by one, as opposed to simultaneously. Might not be a super big deal with Soulshift.

About Cycling/Transmute not working with lands... I hacked in a solution and it seems to work alright now, if a land has Cycling/Transmute (or any other ability that's an "Ability_Hand") a dialog will show up with the options "Play Land" and "Cycling:2". If a land has already been played that turn, (and no Exploration / Fastbond is in play), the cycling ability will be used.
I'll submit this to the SVN asap, but currently I'm still in the middle of fixing some more combat bugs.
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: Programming a card

Postby Triadasoul » 11 Dec 2009, 08:04

I tried that code but it doesn't even trigger when creature with Soulshift is destroyed. I also put it in Cardfactory_Creatures so there could be a description for Soulshift, but it doesn't matter.

Cardfactoryutil
Code: Select all
  public static Ability ability_Soulshift(final Card sourceCard, final String Manacost)
  {
    final Ability Soulshift = new Ability(sourceCard,"0")
    {
     private static final long serialVersionUID = -4960704261761785512L;
    
      public void resolve()
      {
         PlayerZone lib = AllZone.getZone(Constant.Zone.Graveyard, sourceCard.getController());
          PlayerZone hand = AllZone.getZone(Constant.Zone.Hand, sourceCard.getController());
                 
        
          CardList cards = new CardList(lib.getCards());
          CardList sameCost = new CardList();
         
          for (int i=0;i<cards.size();i++)
          {
             if( CardUtil.getConvertedManaCost(cards.get(i).getManaCost()) <= CardUtil.getConvertedManaCost(sourceCard.getManaCost()) ) 
             {
                sameCost.add(cards.get(i));
             }
          }

         
          if(sameCost.size() == 0)
            return;

          if (sourceCard.getController().equals(Constant.Player.Human)) {
            Object o = AllZone.Display.getChoiceOptional("Select a card", sameCost.toArray());
            if(o != null)
            {
              //ability.setTargetCard((Card)o);
              //AllZone.Stack.add(ability);
         
              Card c1 = (Card)o;
              lib.remove(c1);
              hand.add(c1);
                         
                           
            }}
            else          //Wiser choice should be here
            {
               Card choice = null;
               sameCost.shuffle();
               choice = sameCost.getCard(0);
               if (!(choice == null)) {
               lib.remove(choice);
                hand.add(choice);}
            }
          }
     
    };
    Soulshift.setDescription("Soulshift "  +Manacost +" - When this permanent is put into a graveyard from play, you may return target Spirit card with converted mana cost " + Manacost + "or less from your graveyard to your hand.");
    Soulshift.setStackDescription(sourceCard +"Soulshift " +sourceCard.getController()+  " may return target Spirit card with converted mana cost " + Manacost + "or less from his graveyard to his hand.");
    return Soulshift;
  }//ability_Soulshift()
I also added there Prowess of the Fair and it also doesn't trigger. Should i add code to other places for this card effects to work?
PS: I' tried to add "destroyCreature_Prowess_of_the_Fair(c, destroyed);" to Soulcatcher trigger and it works, but when i change
Code: Select all
else if (c.getName().equals("Soulcatcher") && destroyed.getKeyword().contains("Flying"))
            destroyCreature_Soulcatcher(c, destroyed);
to
Code: Select all
else if (c.getName().equals("Prowess of the Fair") && destroyed.getKeyword().contains("Flying"))
            destroyCreature_Soulcatcher(c, destroyed);
it doesn't trigger. Whats the matter with it ?

Cards.txt entry:
Code: Select all
Prowess of the Fair
1 B
Tribal Enchantment Elf
Whenever another nontoken Elf is put into your graveyard from the battlefield, you may put a 1/1 green Elf Warrior creature token onto the battlefield.
Triadasoul
 
Posts: 223
Joined: 21 Jun 2008, 20:17
Has thanked: 0 time
Been thanked: 4 times

Re: Programming a card

Postby Triadasoul » 11 Dec 2009, 11:53

Is there an other way to get turn controller other then AllZone.Phase.getActivePlayer() ? (Because it returns opposite player during attack phase effects or abilities microphases)
Triadasoul
 
Posts: 223
Joined: 21 Jun 2008, 20:17
Has thanked: 0 time
Been thanked: 4 times

Re: Programming a card

Postby DennisBergkamp » 12 Dec 2009, 01:15

Hmm, but shouldn't Prowess of the Fair be called through something like this:

Code: Select all
else if (c.getName().equals("Prowess of the Fair") && destroyed.getType().contains("Elf")) && !destroyed.isToken() && !c.equals(destroyed) )
            destroyCreature_Prowess_of_the_Fair(c, destroyed);
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

PreviousNext

Return to Developer's Corner

Who is online

Users browsing this forum: Bing [Bot] and 41 guests

Main Menu

User Menu

Our Partners


Who is online

In total there are 42 users online :: 1 registered, 0 hidden and 41 guests (based on users active over the past 10 minutes)
Most users ever online was 7967 on 09 Sep 2025, 23:08

Users browsing this forum: Bing [Bot] and 41 guests

Login Form