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.