Lilac Soul - Spell-Hooking
Since version 1.59, it has become exceedingly easy to hook spellcasting. Rather than having to edit each individual spell script, all you now how to do is create a single spell script of your own, and then have that script perform whatever you want the spell to do. In that script, you also get to set whether the original spell script should continue or not.
If you only want to edit a single spell, it may still be easier to just edit its spell script, but I still recommend using the spellhook system. Why? Because, if the spell script is updated in a future patch, your edits will still be valid, but so will the ones BioWare made.
However, the new spell-hook system is very useful for editing a number of spells (even all spells), for instance to make it so that they only work under certain conditions, or that, perhaps, the caster automatically takes a certain amount of damage whenever casting a spell. Thus, you can also use the spellhoook system to make certain creatures react to certain spells in different ways, etc. etc. The sky is the limit really.
So how is it done? There are two things you need. First, you need to set a local string on the module to tell the spellhook system which script you want to execute. Remember, that one script will handle all your spell edits, but you can check for spells in it. If the local string isn't found on the module, no userdefined spell script will run, and the normal spell will fire as usual. This means that you can use SetLocalString() and DeleteLocalString() to switch between when you want to use it, and when you don't. If you just want to use your spellhook system all the time in the module, you can set the local string directly in the Toolset:
- Go to the "edit" menu, and choose Module properties
- Under the advanced tab, there'll be a button named "Variables"; click it
- You'll see a window with existing variables on the module (will be empty unless you've set some previously)
- In the "Name" part, write: X2_S_UD_SPELLSCRIPT
- In the Type part, select String
- In the Value part, enter the name of the script you want to execute when a PC casts a spell
(For the purpose of this tutorial, I'm going to call it "myspell")
To enable spell-hooking programmatically, use the following code (altered for your script file name):
SetLocalString(GetModule(), "X2_S_UD_SPELLSCRIPT", "myspell");
// note - no *.nss file extension
And to disable spell-hooking programmically, use the following code:
In the myspell script, the PC casting the spell will be OBJECT_SELF. You can use the following functions to get information about the spell:
GetSpellTargetObject(); // returns the targeted object of the spell, if valid
GetSpellTargetLocation(); // returns the targeted location of the spell, if valid
GetLastSpellCastClass(); // gets the class the PC cast the spell as
GetSpellCastItem(); // if an item cast the spell, this function gets that item
GetSpellSaveDC(); // gets the DC required to save against the effects of the spell
GetCasterLevel(OBJECT_SELF); // gets the level the PC cast the spell as
The final thing you need to know before you can start making your own spellhook scripts is how you stop the original spell script from being run. Simple. At the top of the script, you'll need:
And then, when you decide that it should NOT run the original script after your script, you put this line in your script:
If that command has been called before your script finishes, a local variable will have been set that tells the spellhooking system to stop the original BioWare script from executing.
Alright, I guess it's time for some examples now. Remember to set the local string on the module.
A very simple spellhook script would be one that just disallows casting of spells in certain areas. The following script will disallow all spells cast during daytime:
// Is it day? If so, disallow spells
SendMessageToPC(OBJECT_SELF, "Spells cannot be cast during the day.");
Here is an outline of how a script could look that would handle all sorts of spells differently:
int nSpell = GetSpellId();
int nSpellDC = GetSpellSaveDC();
int nCastLevel= GetCasterLevel(OBJECT_SELF);
/* The Doom spell and the Creeping Doom spell will
deal 3d10 damage to the caster, but will then
continue the original script. Thus, we don't call
/* The crumble spell will kill the PC if cast by day.
If cast by night, it runs normally
ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(), OBJECT_SELF);
/* Bull's strength, burning hands, and call lightning
just run normally - we wouldn't need this if the
default clause below was absent (then all spells
would operate normally).
/* All other spells require the PC to do a will save check
against the spell's DC. If passed, the spell fires as normal
and the PC gains nCastLevel * 5 XP.
If not, the PC is stunned for n rounds, where n is a number
between 1 and the level of the spell
if (!WillSave(OBJECT_SELF, nSpellDC, SAVING_THROW_TYPE_MIND_SPELLS))
} // end switch statement
A final note: All of the above only applies if it is a PC casting the spell. If you want it to also apply for NPCs, you must set a local integer called X2_L_WILD_MAGIC to TRUE on all of the areas (not the module itself) where you want the spellhook system to also affect spells cast by NPCs, including henchmen! Note that, if you set the variables directly in the Toolset, which you might as well, you can't use the value TRUE. Just use the value 1 instead.
It may be a good idea to cache the spellhook script in the module's properties, especially if you enable the spellhooking for NPCs as well. Do not cache scripts as the engine's new automatic caching does a better job.
Article version 1.1, Feb 26 2004
author: Lilac Soul, editor: Charles Feduke