Tag-based scripting is a paradigm for supporting custom items that does not require editing module event scripts for each item. When this system is enabled, the relevant module events execute a script whose name is the item's tag, allowing an item and its script to be cleanly added to or removed from a module with almost no more risk of interference with other items than is normal for adding or removing an item alone. (The risk is reduced to a name conflict, as would occur if adding an item with a ResRef that is already in use.)
The flexibility of tag-based scripting is subject to abuse, particularly when local vault characters are used. To counter this, BioWare's system allows a module to define a prefix for all tag-based script names by storing the prefix in the local string "MODULE_VAR_TAGBASED_SCRIPT_PREFIX". When this local variable is set on the module, then the name of the script invoked by tag-based scripting is not just the item's tag, but the prefix followed by the item's tag. As a side-effect, using this prefix groups all tag-based scripts together in the alphabetical listing of a module's scripts.
SoU system
The Shadows of Undrentide expansion includes a simple tag-based system, covering only two events and not supporting a prefix for the script name. The event handlers in this system are the following.
- OnAcquireItem event:
x0_onitemacqr - OnActivateItem event:
x0_onitemactv
Setting these handlers in the module's properties is all that is required to use this system. These default handlers may be customized by a module that requires additional processing beyond the tag-based system.
With these handlers in place, activating an item's "unique power" will execute the script whose name is the item's tag, ignoring case. This script should only handle item activation.
When an item is acquired (by a PC), the script that is executed has "_aq" appended to its name. That is, if an item's tag is ITEM_TAG, then the script executed when that item is acquired is item_tag_aq. This script – not needed in many cases – should handle only item acquisition. It may be omitted if not needed.
HotU system
The Hordes of the Underdark expansion includes a more sophisticated tag-based system. More events are covered, and all events for the same item are handled in the same script. The event handlers in this system are the following.
- OnAcquireItem event:
x2_mod_def_aqu - OnActivateItem event:
x2_mod_def_act - OnPlayerEquipItem event:
x2_mod_def_equ - OnPlayerUnEquipItem event:
x2_mod_def_unequ - OnUnAcquireItem event:
x2_mod_def_unaqu
There are also two item events triggered by standard scripts (not event handlers).
- On-hit cast: a result of unique power (on hit) via
x2_s3_onhitcast - Spell cast at: part of the standard spellhook, after crafting and sequencer handling
These default handlers may be customized by a module that requires additional processing beyond the tag-based system. (Some of these handlers also have support for the intelligent weapon and horse riding systems.) Furthermore, each may be omitted without interfering with the other events.
In addition to these handlers, this tag-based system needs to be initialized. This can be done by setting a local variable on the module, but this often is not a convenient approach. The intended initialization occurs when the module is loaded.
- OnModuleLoad event:
x2_mod_def_load
This default script has several module "switches" that can be turned on. Most are commented out (turned off) by default, but tag-based scripting is left on. Simply setting this handler will enable tag-based scripting with no prefix. (It also enables UMD checks for scrolls at the "very difficult" and "hardcode" difficulty settings.) A module may modify this script to better suit its needs, such as specifying a prefix for tag-based scripting.
With the system fully configured, each of the five item events will execute a script whose name is the designated prefix followed by the relevant item's tag (truncated to 16 characters). This script is responsible for determining which event caused it to be executed, which is accomplished via GetUserDefinedItemEventNumber(), defined in the x2_inc_switches include file. This function returns a X2_ITEM_EVENT_* constant indicating which event is occurring.
Examples
The standard script x2_it_example demonstrates how to use tag-based scripting.
Another approach is the following.
If just activation is needed:
// Tag-based script template, simplified version.
// This is intended to be a starting point for writing a tag-based script for
// an item that only needs to handle its activation event.
#include "x2_inc_switches"
void main()
{
int nEvent = GetUserDefinedItemEventNumber();
// Spells might continue to their spell scripts. All other events are
// completely handled by this script.
if ( nEvent != X2_ITEM_EVENT_SPELLCAST_AT )
SetExecutedScriptReturnValue();
// We are only interested in activation.
if ( nEvent != X2_ITEM_EVENT_ACTIVATE )
return;
// Define the parameters.
object oItem = GetItemActivated();
object oTarget = GetItemActivatedTarget();
location lTarget = GetItemActivatedTargetLocation();
object oActivator = GetItemActivator();
// -------------------------------------------------------------------------
// At this point, OBJECT_SELF will refer to the module.
// The item activated is oItem, the activator is oActivator, the target
// object is oTarget, and the target location is lTarget.
// If oTarget is not OBJECT_INVALID, then lTarget is oTarget's location.
// If oTarget is OBJECT_INVALID, then the player clicked on the ground.
//
// Begin activation code here.
}All events covered:
// Tag-based script template, full version.
#include "x2_inc_switches"
// The individual event handlers (declarations).
void OnAcquire(object oItem, object oAcquiredBy, object oTakenFrom, int nStackSize);
void OnActivate(object oItem, object oTarget, location lTarget, object oActivator);
void OnEquip(object oItem, object oEquippedBy);
void OnHit(object oItem, object oTarget, object oCaster);
int OnSpellCast(object oItem, int nSpell, object oCaster);
void OnUnacquire(object oItem, object oLostBy);
void OnUnequip(object oItem, object oUnequippedBy);
// The main function.
void main()
{
int nEvent = GetUserDefinedItemEventNumber();
// Spells might continue to their spell scripts. All other events are
// completely handled by this script.
if ( nEvent != X2_ITEM_EVENT_SPELLCAST_AT )
SetExecutedScriptReturnValue();
// Determine which event triggered this script's execution.
switch ( nEvent )
{
// Item was acquired.
case X2_ITEM_EVENT_ACQUIRE:
OnAcquire(GetModuleItemAcquired(), GetModuleItemAcquiredBy(),
GetModuleItemAcquiredFrom(), GetModuleItemAcquiredStackSize());
break;
// Item was activated ("activate item" or "unique power").
case X2_ITEM_EVENT_ACTIVATE:
OnActivate(GetItemActivated(), GetItemActivatedTarget(),
GetItemActivatedTargetLocation(), GetItemActivator());
break;
// Item was equipped by a PC.
case X2_ITEM_EVENT_EQUIP:
OnEquip(GetPCItemLastEquipped(), GetPCItemLastEquippedBy());
break;
// Item is a weapon that just hit a target, or it is the armor of someone
// who was just hit.
case X2_ITEM_EVENT_ONHITCAST:
OnHit(GetSpellCastItem(), GetSpellTargetObject(), OBJECT_SELF);
break;
// A PC (or certain NPCs) cast a spell at the item.
case X2_ITEM_EVENT_SPELLCAST_AT:
if ( OnSpellCast(GetSpellTargetObject(), GetSpellId(), OBJECT_SELF) )
SetExecutedScriptReturnValue();
break;
// Item was unacquired.
case X2_ITEM_EVENT_UNACQUIRE:
OnUnacquire(GetModuleItemLost(), GetModuleItemLostBy());
break;
// Item was unequipped by a PC.
case X2_ITEM_EVENT_UNEQUIP:
OnUnequip(GetPCItemLastUnequipped(), GetPCItemLastUnequippedBy());
break;
}
}
// -----------------------------------------------------------------------------
// Next, define the individual event handlers.
// -----------------------------------------------------------------------------
// oItem was acquired (by a PC or an NPC).
// Run by the module.
void OnAcquire(object oItem, object oAcquiredBy, object oTakenFrom, int nStackSize)
{
}
// oItem was activated ("activate item" or "unique power").
// Run by the module.
void OnActivate(object oItem, object oTarget, location lTarget, object oActivator)
{
}
// oItem was equipped by a PC.
// Run by the module.
void OnEquip(object oItem, object oEquippedBy)
{
}
// oItem is a weapon that just hit a target, or it is the armor of someone who
// was just hit by someone else's weapon.
// Run by the caster.
void OnHit(object oItem, object oTarget, object oCaster)
{
}
// Someone cast a spell at oItem.
// This usually only fires if a PC cast the spell, but it also fires for
// DM-possessed NPCs and NPCs in an area with the "X2_L_WILD_MAGIC" local integer set.
//
// Return TRUE to prevent the spell script from firing.
// Return FALSE to proceed normally.
//
// This fires after the UMD check, module spellhook, item creation, and
// sequencer handlers, even if any of those decided to handle this spell.
// This fires before the check to see if this is a spell that normally can
// target items (and before the spell script itself runs).
//
// Run by the caster.
int OnSpellCast(object oItem, int nSpell, object oCaster)
{
return FALSE;
}
// oItem was unacquired/lost (by a PC or NPC).
// Run by the module.
void OnUnacquire(object oItem, object oLostBy)
{
}
// oItem was unequipped by a PC.
// Run by the module.
void OnUnequip(object oItem, object oUnequippedBy)
{
}