Author Topic: Tag-based scripting that doesn't suck!  (Read 293 times)

Legacy_Squatting Monk

  • Hero Member
  • *****
  • Posts: 776
  • Karma: +0/-0
Tag-based scripting that doesn't suck!
« on: August 22, 2015, 01:17:09 am »


               I discovered a fun trick when using tag-based scripts. I'm sure it's common knowledge to some folks, but I figured I'd throw it out there for those who didn't already know.
 
Suppose you're scripting a system that makes use of several different items, and you want them to do different things when used. One option would be to give them all different tags and use a tag-based script for each one. This is fine if you only have a couple of items. But with a lot of them, this has the effect of bloating your module's script count and duplicating a lot of effort. If a builder wants to expand your system, he has to create a new item and a new script. That sucks!
 
Another option would be to give them all the same tag, use a tag-based script to get a local variable from the item, and vary the behavior based on the local. That's a good option for most use cases. However, local variables are stripped from items when characters are saved to a local vault. This means your items won't work correctly if you port the PC to another module, even if the new module has the same system and same tag-based script. That sucks!
 
The other option that I see commonly used is to ditch tag-based scripts altogether and modify the module's event scripts to check if the tag is one of several values, then run a custom script based on that. For example: 
// module event script
if (sTag == "item1" || sTag == "item2" || sTag == "item3")
    ExecuteScript("my_script", oItem);


// my_script.nss
if (sTag == "item1")
    // Do something
else if (sTag == "item2")
    // Do something else
// etc. 
This has the advantage of consolidating your scripts and making it a bit easier to add new items to the system. However, if you have multiple systems that do this, you're going to have to merge the module event scripts, which is a pain, especially for non-scripters. Plus, these checks are going to be run every single time any item is acquired/activated/etc., regardless of whether the item is from your system. That sucks!
 
But there is a better way!
 
You see, tags have a maximum of 32 characters, but script names have a maximum of 16 characters. Thus, only the first 16 characters of the tag determine which script runs. But you can use the remaining characters to change the behavior of the item. For example: 
// mysixteenchartag.nss
// ...

    string sTag  = GetTag(oItem);
    string sSub  = GetStringRight(sTag, GetStringLength(sTag) - 16);

    SendMessageToPC(oPC, "This item is tagged " + sTag + " but its subtag is " + sSub);
// ...
Now we could create several items tagged mysixteenchartag_NiftyItem1, mysixteenchartag_NiftyItem2, and mysixteenchartag_NiftyItem3. All of them will run the same tag-based script, but we can have different behavior for each one conditional on the sub-tag:
// ...

if (sSub == "_NiftyItem1")
    ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(20, DAMAGE_TYPE_FIRE), oPC);
else if (sSub == "_NiftyItem2")
    ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(), oPC);
else if (sSub == "_NiftyItem3")
    SendMessageToPC(oPC, "Hmm... that didn't seem to do anything.");

// ...
Parsing the subtag could allow you to refine this behavior even further.

Obviously, this isn't the best option for every use case. Without NWNX, tags can only be changed by copying an item, so a system that needs items to change their state should probably use locals instead. And someone who's only making one or two items doesn't need to worry about script bloat, so making separate tag-based scripts will work in those cases. Hopefully this info will be useful to anyone making a large number of different items, though.
               
               

               
            

Legacy_Tarot Redhand

  • Hero Member
  • *****
  • Posts: 4165
  • Karma: +0/-0
Tag-based scripting that doesn't suck!
« Reply #1 on: August 22, 2015, 03:07:22 am »


               

I think that using the name (getname/setname) could be a viable alternative for items that need to change their state without recourse to NWNX.


 


TR



               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Tag-based scripting that doesn't suck!
« Reply #2 on: August 22, 2015, 03:18:01 am »


               

im using same tag and then resref to change the behavior of the specific items



               
               

               
            

Legacy_Squatting Monk

  • Hero Member
  • *****
  • Posts: 776
  • Karma: +0/-0
Tag-based scripting that doesn't suck!
« Reply #3 on: August 22, 2015, 03:18:21 am »


               

I think that using the name (getname/setname) could be a viable alternative for items that need to change their state without recourse to NWNX.

True, as long as you watch out for any system that lets players modify item names. Otherwise you open yourself to exploits.

im using same tag and then resref to change the behavior of the specific items

This is a good option, too, as long as you're not hurting for blueprint space.
               
               

               
            

Legacy_henesua

  • Hero Member
  • *****
  • Posts: 6519
  • Karma: +0/-0
Tag-based scripting that doesn't suck!
« Reply #4 on: August 22, 2015, 03:54:08 am »


               

I use subtags also, but was not clever as you have been with the number of characters. I created a function which extracts the subtag (everything to the left of the first underscore). Its a bit of overhead but doesn't seem to be too problematic.


 


One thing to keep in mind with this is that there is an option to use a tag prefix for the tag based script, and so unless you account for that you can run into errors.



               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Tag-based scripting that doesn't suck!
« Reply #5 on: August 22, 2015, 06:56:15 am »


               


This is a good option, too, as long as you're not hurting for blueprint space.




Im normally trying to keep resref and tag equal. That means I need to plan my item identificators in a way they dont exceed 16 characters. And I never had a problem with that actually, in many cases, if I would want to use your method of separating different items in tagbases scripting, I would have to use several nulls or other characters only to extend the tag to 16 characters.


Ie. currently say i have this


tag: sh_medkit, sh_medkit, sh_medkit


resref: sh_medkit001, sh_medkit002, sh_medkit003


 


If I wanted to use your method, then:


tag: shadooow_medkit_1, shadooow_medkit_2, shadooow_medkit_3


resref: sh_medkit001, sh_medkit002, sh_medkit003


 


Its really needless at least because resref cant match tag anyway and resref has to be 16 characters long max.



               
               

               
            

Legacy_Fester Pot

  • Hero Member
  • *****
  • Posts: 1698
  • Karma: +0/-0
Tag-based scripting that doesn't suck!
« Reply #6 on: August 22, 2015, 02:36:09 pm »


               

I use the tag/resref option myself for tagged based scripting. Since it was Axe Murderer who taught me about the feature, I grew up as a builder using his examples.


Your trick is nifty though and very helpful. Thanks!


FP!



               
               

               
            

Legacy_kalbaern

  • Hero Member
  • *****
  • Posts: 1531
  • Karma: +0/-0
Tag-based scripting that doesn't suck!
« Reply #7 on: August 22, 2015, 04:44:21 pm »


               

Another option with tag based scripting is to check the appearance ID(s) and use a different appearance for each variation. I use this primarily with non-parts based items, especially my various "food" items and such.



               
               

               
            

Legacy_Shadooow

  • Hero Member
  • *****
  • Posts: 7698
  • Karma: +0/-0
Tag-based scripting that doesn't suck!
« Reply #8 on: August 23, 2015, 06:49:38 am »


               

Just realized one advantage of Squatting Monk's solution.


 


I have a custom set of containers called "smart bags" (I plan to release them on vault soon). Majority of them is doing single actions, they have unique power and when player uses them they rearrange inventory and move certain type of items inside itself. However some of them has improved version like the one for traps which automatically moves any recovered traps inside itself.


 


Problem I had while scripting this is that every of these smart bags has a same tag, thus I couldnt use GetItemPossessedBy function to easily find whether player has "improved smart box for traps" and had to loop whole inventory and search by tag.


 


It is of course easy to workaround, but definitely worth to mention 'B)' .