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:


  1. Go to the "edit" menu, and choose Module properties
  2. Under the advanced tab, there'll be a button named "Variables"; click it
  3. You'll see a window with existing variables on the module (will be empty unless you've set some previously)
  4. In the "Name" part, write:
    X2_S_UD_SPELLSCRIPT
  5. In the Type part, select String
  6. 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):


// note: you do not need to use this code to run this example
SetLocalString(GetModule(), "X2_S_UD_SPELLSCRIPT", "myspell");
// note - no *.nss file extension

And to disable spell-hooking programmically, use the following code:


// note: you do not need to use this code to run this example
DeleteLocalString(GetModule(), "X2_S_UD_SPELLSCRIPT");

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:

GetSpellId(); // returns the SPELL_* constant of the spell cast
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 do you stop the original spell script from being run. Simple. At the top of the script, you'll need:


#include "x2_inc_switches"

And then, when you decide that it should NOT run the original script after your script, you put this line in your script:


SetModuleOverrideSpellScriptFinished();

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:


#include "x2_inc_switches"

void main()
{
     //Is it day? If so, disallow spells
     if (GetIsDay())
     {
          SetModuleOverrideSpellScriptFinished();
          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:

#include "x2_inc_switches"

void main()
{
     int nSpell=GetSpellId();
     int nSpellDC=GetSpellSaveDC();
     int nCastLevel=GetCasterLevel(OBJECT_SELF);

     switch (nSpell)
     {
          case SPELL_CREEPING_DOOM:
          case SPELL_DOOM:
               /* The doom spell and the creeping doom spell will 
		  deal 3d10 damage to the caster but it will then 
                  continue the original script, thus we don't call
                  SetModuleOverrideSpellScriptFinished();
               */
               ApplyEffectToObject(DURATION_TYPE_INSTANT,
                          EffectDamage(d10(3), DAMAGE_TYPE_DIVINE, DAMAGE_POWER_ENERGY),
                          OBJECT_SELF);
          break;

          case SPELL_CRUMBLE:
               /* The crumble spell will kill the PC if cast by day.
                  If cast by night, it runs normally
               */
               if (GetIsDay())
               {
                    ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDeath(), OBJECT_SELF);
                    SetModuleOverrideSpellScriptFinished();
               }
          break;
          
          case SPELL_BULLS_STRENGTH:
          case SPELL_BURNING_HANDS:
          case SPELL_CALL_LIGHTNING:
               /* 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).
               */
               break;

          default:
          /* 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))
          {
               ApplyEffectToObject(DURATION_TYPE_TEMPORARY,
                             EffectStunned(),
                             OBJECT_SELF,
                             IntToFloat(Random(nCastLevel)+1));
               SetModuleOverrideSpellScriptFinished();
          }
          else GiveXPToCreature(OBJECT_SELF, nCastLevel*5);
     
     } // 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 Tollset, 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.


Article version 1.1, Feb 26 2004




 author: Lilac Soul, editor: Charles Feduke