It is currently 31 Oct 2025, 16:03
   
Text Size

spDestroyTgt keyword

Post MTG Forge Related Programming Questions Here

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

Re: spDestroyTgt keyword

Postby MageKing17 » 05 Jul 2009, 21:33

I had some similar problems when creating the new match objects for Incantus (I wanted them to make their own description strings, so isCreature.isColor("U").isSubtype("Faerie").isSupertype("Legendary") would turn into "legendary blue Faerie creature"), and originally decided that if you wanted to use OR logic, you'd have to do things the hard way. Then Incantus had an idea that was something along the lines of:
Code: Select all
isCreature.with(colors="U", subtypes="Faerie", supertypes="Legendary").or(colors="R", subtypes="Goblin", supertypes="Snow")
I shied away from the massive effort it would take to rewrite the match objects again, but I had to admit it would work quite well. I'm not sure if something completely analogous could work in MTG Forge, but maybe our thoughts on the matter can help you come up with a solution.
User avatar
MageKing17
Programmer
 
Posts: 473
Joined: 12 Jun 2008, 20:40
Has thanked: 5 times
Been thanked: 9 times

Re: spDestroyTgt keyword

Postby zerker2000 » 06 Jul 2009, 00:01

As much as I understand it, we already have a very efficient CardListFilter method that works with native java boolean logic, however I think the topic here is making an easily-parseable easily-writable keyword interface, e.g(for your example)
Code: Select all
Goblin/Faerie Destruction
Instant
B
no text
spDestroyTgt: Legendary Blue Faerie Creature or Snow Red Goblin Creature
Speaking(writing) of which, wouldn't it be possible in most cases to just duplicate any global "and" values? Examples for oracle texts are:
Them Wizards wrote:Destroy target artifact or enchantment.
Destroy target creature with power 4 or greater
Destroy target artifact, creature, or land.
Destroy target red permanent.
Destroy target land or nonblack creature.
Destroy target non-Swamp land.
Destroy target tapped creature.
Destroy target Wall.
Destroy target noncreature permanent.
Destroy target creature that isn't enchanted.
<when deals cmbDmg to a player, you may>destroy target artifact that player controls.
Destroy target nonsnow creature;
Destroy target creature with a -1/-1 counter on it.
Destroy target Scarecrow or Plains.
Destroy target nonblack attacking creature.
Destroy target nonblack creature that came into play this turn.
Destroy target creature blocking or blocked by Cromat.
Destroy target Aura attached to a creature.
Destroy target creature token.
Destroy target creature <you/an opponent> controls.
Destroy target artifact, creature, or land you control.
Destroy target non-Zombie creature.
Destroy target artifact or creature with converted mana cost X.
Destroy target Creature with shadow.
Destroy silver-bordered permanent in any game you can see from your seat.
(OK maybe not the last one)
I just went through gatherer searching for "destroy target", got to about the E's, and it seems that an easy way to parse a bunch of oracle "spDestroyTgt" keywords would be:
Code: Select all
//orig is the original keyword string
//we're in "inPlay.filter(new cardListFilter(){boolean addcard(Card card){"
boolean res=false;
if(!orig.startsWith("Destroy target)) throw(new Exception(/*insert message here*/);
destroyKeyword=orig.substring(15);//removes "Destroy target "
destroyKeyword=destroyKeyword.replaceAll(",", "or");
orparse:
for(String m : destroyKeyword.split("or"))
{
if(res) break;
m=m.trim()
boolean with=false;
for(String s: m.split(" "))
{
if(s.equals("with")){with=true; continue;}
if(with)
{
with = false;
s=(s.charAt(0)+"").toUppercase()+s.substring(1);//Keywords are uppercase(I think)
if(card.getKeywords().contains(s))continue;
continue orparse;
}
boolean match=false;
boolean non=s.contains("non");
if(non) s=s.substring(3)
if(s.startsWith("-")) s=s.substring(1);
for(String color : Constant.Colors.onlyColors)
{
if(!s.equals(color)) continue;
String short = Input_PayManaCostUtil.getColor2(color)//I think
if(!card.getManaCost.contains(short)continue;
match = true;
break;
}
s=(s.charAt(0)+"").toUppercase()+s.substring(1);//types are stored as uppercase, right?
if(!match) match = card.typeContains(s);
if (non) match=!match;//I think there's  better way to do this
if(match)continue;
continue orparse;
}
res=true;
}
return res;
WARNING(/explanation): I did not test any of it, I did not spell/varcheck any of it, I did not indent any of it... Just basically, I composed the entire thing from scratch while posting the reply as proof of concept. Heavy modifications are probably required for it to work at all :P.
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: spDestroyTgt keyword

Postby Rob Cashwalker » 06 Jul 2009, 02:27

I can't seem to follow your code. I'm not a fan of free-form parsing, though I'm sure it could be done.

I'm leaning to some form of the following:
Terror spDestroyTgt:Creature.nonBlack+nonArtifact:NoRegen
Naturalize spDestroyTgt:Artifact,Enchantment
Creeping Mold spDestroyTgt:Artifact,Enchantment,Land
Deathmark spDestroyTgt:Creature.Green,Creature.White

Commas separate the inclusive restrictions, periods and plus signs separate the exclusive restrictions. So for Naturalize, it will pull a list of Artifacts and a list of Enchantments, then combine them (Creeping Mold is similar). For Terror, it will pull a list of Creatures, then filter that list for nonBlack and nonArtifact.
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: spDestroyTgt keyword

Postby zerker2000 » 06 Jul 2009, 03:44

Rob Cashwalker wrote:I can't seem to follow your code. I'm not a fan of free-form parsing, though I'm sure it could be done.
I very much am, and envision Forge(someday) being able to ReadCard Allsets.txt(official one; attached) and import 90% of the cards correctly...parsing text that follows rigid formatting isn't Rocket Science :-".
Attachments
Allsets-2009-03-21.zip
A text file containing oracle texts for all Magic cards as of March, zipped.
(369.6 KiB) Downloaded 528 times
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: spDestroyTgt keyword

Postby Rob Cashwalker » 06 Jul 2009, 12:03

zerker2000 wrote: I very much am, and envision Forge(someday) being able to ReadCard Allsets.txt(official one; attached) and import 90% of the cards correctly...parsing text that follows rigid formatting isn't Rocket Science :-".
Rares has the same delusions of grandeur.... No, it's not rocket science - rockets are easy. I have parsed text quite often in VB and am very good at picking out patterns that I can use to take some shortcuts. A number of those shortcuts just don't work that way in the structure of Java, and we're still left with huge if blocks that spread the relevant code far apart from each other that makes it less readable.
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: spDestroyTgt keyword

Postby MageKing17 » 06 Jul 2009, 21:09

Rob Cashwalker wrote:
zerker2000 wrote: I very much am, and envision Forge(someday) being able to ReadCard Allsets.txt(official one; attached) and import 90% of the cards correctly...parsing text that follows rigid formatting isn't Rocket Science :-".
Rares has the same delusions of grandeur.... No, it's not rocket science - rockets are easy. I have parsed text quite often in VB and am very good at picking out patterns that I can use to take some shortcuts. A number of those shortcuts just don't work that way in the structure of Java, and we're still left with huge if blocks that spread the relevant code far apart from each other that makes it less readable.
I also have the same delusion for Incantus... I have spent quite some time talking about how much it would simplify things if the program could look at "Nightmare's power and toughness are each equal to the number of Swamps you control" and know what it was supposed to do. I even started looking into parsers, but unfortunately Pybison (suggested by telengard) doesn't work with Windows.

If anyone knows a good parser that runs on Windows, Linux, and Mac, give me a shout. :P
User avatar
MageKing17
Programmer
 
Posts: 473
Joined: 12 Jun 2008, 20:40
Has thanked: 5 times
Been thanked: 9 times

Re: spDestroyTgt keyword

Postby Arch » 07 Jul 2009, 09:00

MageKing17 wrote:
Rob Cashwalker wrote:
zerker2000 wrote: I very much am, and envision Forge(someday) being able to ReadCard Allsets.txt(official one; attached) and import 90% of the cards correctly...parsing text that follows rigid formatting isn't Rocket Science :-".
Rares has the same delusions of grandeur.... No, it's not rocket science - rockets are easy. I have parsed text quite often in VB and am very good at picking out patterns that I can use to take some shortcuts. A number of those shortcuts just don't work that way in the structure of Java, and we're still left with huge if blocks that spread the relevant code far apart from each other that makes it less readable.
I also have the same delusion for Incantus... I have spent quite some time talking about how much it would simplify things if the program could look at "Nightmare's power and toughness are each equal to the number of Swamps you control" and know what it was supposed to do. I even started looking into parsers, but unfortunately Pybison (suggested by telengard) doesn't work with Windows.

If anyone knows a good parser that runs on Windows, Linux, and Mac, give me a shout. :P
Even though it's starting to feel like this thread is being hijacked I'll add my thoughts here.

You'd still have to implement all the functionality related to whatever you're parsing, but the actual parsing would just be mapping of these functions to card-objects. I don't belive it would mean more work than doing it another way; that mapping still has to be done. You'd possibly go about the design of it all a bit different though.

About cross-platform parsing. Doesn't feel like you'd need anything really advanced in this case. Mapping reg-exp to functions/objects (however you implement that functionality) seems like the simplest way. Unless you're after a way to teach your program that Nightmare should have P/T = count(Swamps) without you explicitly telling it _how_ to do it; but that's another ball-game all together.

Python provides "shlex" for parsing, not sure that's what you're looking for though. Either way Python was pretty much made for text-processing. :)
User avatar
Arch
Programmer
 
Posts: 206
Joined: 04 Jul 2009, 09:35
Has thanked: 0 time
Been thanked: 15 times

Re: spDestroyTgt keyword

Postby zerker2000 » 07 Jul 2009, 09:43

Unless you're after a way to teach your program that Nightmare should have P/T = count(Swamps) without you explicitly telling it _how_ to do it; but that's another ball-game all together.
Nightmare shouldnt be too hard for a parser to follow: "equal to" means you need to evaluate what follows, "the number of" means we are going to need a natural number, "Swamps in play" means we are looking for cards in the AllZone.Play-s, that match "Swamp"; "Nightmare" get swapt with "{name}", "{name]'s" obviously refer to characteristics, "Power and Toughness" means we are refering the characteristics "Power" and Toughness"... it really shouldn't be very difficult :-".
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: spDestroyTgt keyword

Postby Rob Cashwalker » 07 Jul 2009, 15:42

Nightmare and other */* creatures might be the easiest corner case you could pick. Until you realize that MTGForge needs to evaluate the P/T every time state based effects are checked. So, what would you do parse the card each time?

I've finalized a specification for the targeting restriction. I moved the getValidCards into the CardList object and made it more generic to basically become a filter. So it works equally well for cards in play for Destroy Target, or let's say tutoring for a subset of types of cards, or to filter the opponents hand for nonLand cards to be discarded, etc.
It will follow this spec:
Type1[.restriction1[+restriction2...+restrictionN]][,Type2[.restriction1...]]
ie: Creature.nonBlack+nonArtifact
Artifact,Enchantment,Land
Creature.Green,Creature.White
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: spDestroyTgt keyword

Postby MageKing17 » 07 Jul 2009, 17:59

Rob Cashwalker wrote:Nightmare and other */* creatures might be the easiest corner case you could pick. Until you realize that MTGForge needs to evaluate the P/T every time state based effects are checked. So, what would you do parse the card each time?
Ideally, you wouldn't re-parse the entire card every timestep, and instead have a more computer-readable list of "arguments" for the ability, and only check for when those are changed (for instance, if "Swamp" gets changed to "Plains" by a text-changing effect).

Arch wrote:You'd still have to implement all the functionality related to whatever you're parsing, but the actual parsing would just be mapping of these functions to card-objects. I don't belive it would mean more work than doing it another way; that mapping still has to be done. You'd possibly go about the design of it all a bit different though.
It would almost certainly mean less work total, since so many abilities are copied (with little or no changes) between large numbers of cards. Even if we have to do all the really advanced abilities individually (and I think at least a few of them could fall under a parser), we're no worse off than currently (in Incantus, where we have to do every ability except for keywords by hand).

Also, I suppose good ol' regular expressions could do the trick, but the syntax of Pybison seemed interesting. Oh well, I suppose a regexp would be more cross-platform... and easier for me to code, since I've used them (in Perl) before.

Perhaps we should split this off into a different thread.
User avatar
MageKing17
Programmer
 
Posts: 473
Joined: 12 Jun 2008, 20:40
Has thanked: 5 times
Been thanked: 9 times

Re: spDestroyTgt keyword

Postby zerker2000 » 08 Jul 2009, 04:34

MageKing17 wrote:Ideally, you wouldn't re-parse the entire card every timestep, and instead have a more computer-readable list of "arguments" for the ability, and only check for when those are changed (for instance, if "Swamp" gets changed to "Plains" by a text-changing effect).
Yes, the parsing results should be stored as an Input[] in the ability, and the parser should be run every time the relevant line of cardText is changed.

EDIT:
Perhaps we should split this off into a different thread.
Naah, how to parse ability description text is relevant enough to the thread's purpose ... after all, the main challenge for making spDestroyTgt was exactly that.
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: spDestroyTgt keyword

Postby Rob Cashwalker » 08 Jul 2009, 05:01

Take 2:

Add to CardList:
Code: Select all
    public CardList getValidCards(String Restrictions[])
    {
       CardList tmpList = new CardList(toArray());
       CardList retList = new CardList();
      
       for (int i=0;i<Restrictions.length;i++)
       {
        String incR[] = Restrictions[i].split("\\.");   // Inclusive restrictions are Card types
        
        if (! incR[0].equals("Permanent"))         // Since the cards don't actually say "Permanent"
           tmpList = getType(incR[0]);
        
        if (incR.length > 1)
        {
           final String excR = incR[1];
           tmpList = tmpList.filter(new CardListFilter()
           {
              public boolean addCard(Card c)
              {
                 boolean r = true;
                 String exR[] = excR.split("\\+");   // Exclusive Restrictions are ...
                 for (int j=0;j<exR.length;j++)
                 {
                    if (exR[j].contains("White") ||      // ... Card colors
                          exR[j].contains("Blue") ||
                          exR[j].contains("Black") ||
                          exR[j].contains("Red") ||
                          exR[j].contains("Green") ||
                          exR[j].contains("Colorless"))
                       if (exR[j].startsWith("non"))
                          r = r && (! CardUtil.getColors(c).contains(exR[j].substring(3).toLowerCase()));
                       else
                          r = r && (CardUtil.getColors(c).contains(exR[j].toLowerCase()));
                    else if (exR[j].contains("MultiColor"))   // ... Card is multicolored
                       if (exR[j].startsWith("non"))
                          r = r && (CardUtil.getColors(c).size() == 1);
                       else
                          r = r && (CardUtil.getColors(c).size() > 1);
                    else if (exR[j].contains("with"))   // ... Card keywords
                       if (exR[j].startsWith("without"))
                          r = r && (! c.getKeyword().contains(exR[j].substring(7)));
                       else
                          r = r && (c.getKeyword().contains(exR[j].substring(4)));
                    //TODO: converted mana cost
                    //TODO: tapped
                    //TODO: enchanted
                    //TODO: enchanting
                    //TODO: token
                    //TODO: counters
                    else
                       if (exR[j].startsWith("non"))   // ... Other Card types
                          r = r && (! c.getType().contains(exR[j].substring(3)));
                       else
                          r = r && (c.getType().contains(exR[j]));
                 }
                 return r;
              }
           });
        }
        retList.addAll(tmpList.toArray());
       }
       return retList;
    }//getValidCards

CardFactory:
Code: Select all
 // Generic destroy target card
    if (shouldSpDestroyTgt(card) != -1)
    {
       int n = shouldSpDestroyTgt(card);
       
       String parse = card.getKeyword().get(n).toString();
        card.removeIntrinsicKeyword(parse);
       
        String k[] = parse.split(":");
        String Targets = k[1];   // Artifact, Creature, Enchantment, Land, Permanent, White, Blue, Black, Red, Green, Colorless, MultiColor
                          // non-Artifact, non-Creature, non-Enchantment, non-Land, non-Permanent,
                          //non-White, non-Blue, non-Black, non-Red, non-Green, non-Colorless, non-MultiColor
        final String Tgts[] = Targets.split(",");
       
        String tmpDesc = card.getText().substring(15);
        int i = tmpDesc.indexOf(".");
        tmpDesc = tmpDesc.substring(0, i);
        final String Selec = new String("Select target " + tmpDesc + " to destroy.");
      
      final boolean NoRegen = (k.length == 3);
      
        card.clearSpellAbility();
       
        final SpellAbility spDstryTgt = new Spell(card)
        {
         private static final long serialVersionUID = 142142142142L;
         
         public boolean canPlayAI()
         {
            CardList results = new CardList();
            CardList choices = getTargets();
            
            if (choices.size() > 0)
            {
               for (int i = 0; i < Tgts.length; i++)
               {
                  if (Tgts[i].equals("Artifact"))
                     results.add(CardFactoryUtil.AI_getBestArtifact(choices));
                  else if (Tgts[i].equals("Creature"))
                     results.add(CardFactoryUtil.AI_getBestCreature(choices));
                  else if (Tgts[i].equals("Enchantment"))
                     results.add(CardFactoryUtil.AI_getBestEnchantment(choices, card, true));
                  else if (Tgts[i].equals("Land"))
                     results.add(CardFactoryUtil.AI_getBestLand(choices));
                  else if (Tgts[i].equals("Permanent"))
                     results.add(CardFactoryUtil.AI_getMostExpensivePermanent(choices, card, true));
               }
            }
            if (results.size() > 0)
            {
               results.shuffle();
               setTargetCard(results.get(0));
               return true;
            }
            return false;
         }
         CardList getTargets()
         {
            CardList tmpList = new CardList();
            tmpList.addAll(AllZone.Human_Play.getCards());
            tmpList.filter(new CardListFilter()
            {
               public boolean addCard(Card c)
               {
                  return (CardFactoryUtil.canTarget(card, c));
               }
            });
            
            return tmpList.getValidCards(Tgts);
         }
         public void resolve()
           {
              if (AllZone.GameAction.isCardInPlay(getTargetCard()) &&  CardFactoryUtil.canTarget(card, getTargetCard()))
                 if (NoRegen)
                    AllZone.GameAction.destroyNoRegeneration(getTargetCard());
                 else
                    AllZone.GameAction.destroy(getTargetCard());
              
           }
        }; //SpDstryTgt
       
        Input InGetTarget = new Input()
        {
           private static final long serialVersionUID = -142142142142L;
           
           public void showMessage()
           {
              CardList allCards = new CardList();
              allCards.addAll(AllZone.Human_Play.getCards());
              allCards.addAll(AllZone.Computer_Play.getCards());
              allCards.filter(new CardListFilter()
            {
               public boolean addCard(Card c)
               {
                  return (CardFactoryUtil.canTarget(card, c));
               }
            });
              
              CardList choices = allCards.getValidCards(Tgts);
              stopSetNext(CardFactoryUtil.input_targetSpecific(spDstryTgt, choices, Selec, true));
           }
        };//InGetTarget
       
        spDstryTgt.setBeforePayMana(InGetTarget);
        card.addSpellAbility(spDstryTgt);
    }//spDestroyTgt
Add to CardFactoryUtil:
Code: Select all
public static Card AI_getBestLand(CardList list)
  {
     CardList land = list.getType("Land");
     if (! (land.size() > 0))
        return null;
    
     CardList nbLand = land.filter(new CardListFilter ()   // prefer to target non basic lands
      {
        public boolean addCard(Card c)
           {
              return (!c.getType().contains("Basic"));
           }
      }
     );
    
     if (nbLand.size() > 0)
     {
        //TODO: Rank non basics?
       
        Random r = new Random();
        return nbLand.get(r.nextInt(nbLand.size()));
     }
    
     // if no non-basic lands, target the least represented basic land type
     String names[] = {"Plains", "Island", "Swamp", "Mountain", "Forest"};
     String sminBL = new String();
     int iminBL = 20000; // hopefully no one will ever have more than 20000 lands of one type....
     int n = 0;
     for (int i = 0; i < 5; i++)
     {
        n = land.getType(names[i]).size();
        if (n < iminBL && n > 0)   // if two or more are tied, only the first one checked will be used
        {                     
           iminBL = n;
           sminBL = names[i];
        }
     }
     if (iminBL == 20000)
        return null;   // no basic land was a minimum
    
     CardList BLand = land.getType(sminBL);
     for (int i=0; i<BLand.size(); i++)
        if (!BLand.get(i).isTapped())      // prefer untapped lands
           return BLand.get(i);
    
     Random r = new Random();
     return BLand.get(r.nextInt(BLand.size()));   // random tapped land of least represented type
  }
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: spDestroyTgt keyword

Postby Rob Cashwalker » 09 Jul 2009, 03:12

After a little testing, I made a slight change in the code:
CardFactory:
Code: Select all
        spDstryTgt.setBeforePayMana(InGetTarget);
        spDstryTgt.setDescription(card.getText());
        card.setText("");
        card.addSpellAbility(spDstryTgt);
I realized cycling needs to either be at the end of the CardFactory entirely, or at least at the end of our keyword scripts in order to work right. Same with Flashback and Morph. If any of our keyword scripts clear the card's SpellAbilities, then if cycling was at the top (like it was) then cycling also got cleared.
I also see how important it is for a SpellAbility to have a description - if there are two modes of the card, but one of the SpellAbilities has a blank description, then it doesn't show up in the dialog box.

spDestroyTgt uses the plain english card text for the input object, but now the code copies it into the SpellAbility and then trashes it. Not ideal, but it doesn't make sense for it to be printed twice in the card text in the GUI.
Maybe the better solution is to modify Card.getText() to filter SpellAbility descriptions that are already present in the card text. Or add a boolean flag in the SpellAbility object which is checked in Card.getText(), which triggers it to not be used in building the card text that is returned.
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: spDestroyTgt keyword

Postby DennisBergkamp » 24 Jul 2009, 14:54

Rob,

Take 2:
...
I guess this is ready to be merged into the next release then?
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: spDestroyTgt keyword

Postby Rob Cashwalker » 24 Jul 2009, 20:15

DennisBergkamp wrote:Rob,

Take 2:
...
I guess this is ready to be merged into the next release then?
Yeah, taking the change from the additional post above into account.

Also consider comments I've made here and in other threads about the order of processing for Cycling, Morph and "Tack On" abilities.
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

PreviousNext

Return to Developer's Corner

Who is online

Users browsing this forum: No registered users and 38 guests

Main Menu

User Menu

Our Partners


Who is online

In total there are 38 users online :: 0 registered, 0 hidden and 38 guests (based on users active over the past 10 minutes)
Most users ever online was 9298 on 10 Oct 2025, 12:54

Users browsing this forum: No registered users and 38 guests

Login Form