How to override core scripting functions


This article presents a technique that can be used to override NWN Built-In script functions. It does NOT give you any magic knowledge to circumvent hardcoded functionality in NWN, it merely shows how to globally replace core engine script functions in your module with your own versions.


Background Information

These things are useful to keep in mind before reading on further: All NWN script functions are defined in a file called nwscript.nss, which can be found in the game resources. NWScript.nss is used by the toolset to compile your scripts into ncs files. Scripting functions in nwscript.nss are mapped to the built in engine functions based on their position in the file. That means that deleting, adding or moving scripting commands around in the file is going to break every script in the game.

Example:

Swapping Random(int) and PrintString(string) in the file would cause the scripting command 
Random() to behave like PrintString(). 

However, since Random takes an integer as input parameter and PrintString takes a string, the toolset would now fail to compile any script that includes a function call to these functions. The game has the compiled .ncs version for all bioware scripts in the resources. That means that even if you change nwscript.nss, those scripts will not behave differently from how they worked before as they do not get recompiled unless overridden in a module.



A friendly warning…

This article assumes that you have some experience with advance scripting in NWN. Messing around with nwscript.nss (see below) is dangerous and can break every script in your module at once.

NEVER EVER release a modified nwscript with a hakpak or .erf. Don't do it.



Why do this ?

There are a number of good reasons on why you may want to override one of the core engine functions in your module.

  • To add diagnostics functionality (call logging) to an engine function without changing the command syntax.
  • To extend or change the functionality of an existing scripting command (i.e. add code to CreateItemOnObject to use the new patch 1.67 SetName functionality to dynamically change the name of the created object to something else or to add Logging to any kind of BanPlayerByIP call…)
  • To add new parameters to an existing scripting command.
  • To disable a function completely, i.e. if you find it to be a risk on your server
  • Completely replace an existing command (i.e. Change implement your own RandomName() that takes race into account for name generation for 1.67's SetName() functionality and have it replace the existing RandomName() command)


Most of these effects could also be achieved by just creating and calling your own functions from one of your includes, but that means that any script you import into your module from other sources (i.e. the vault) needs to be updated with your function calls and that any additional person that works on your module will need to memorize your redefined function names and remember to use those instead of the real thing.



How to do this

Let's assume you want to Log every call to PrintString() in your module. You could do that by writing your own function MyPrintString(), putting it in an include file (i.e. myinclude.nss) and including this file into every single script you write. That works and is a fine approach for most cases.

But there is another option - universally mapping PrintString() to MyPrintString(), at the source.

Knowing that nwscript.nss holds the definition to PrintString, you can open nwscript.nss, find the definition for PrintString and change it to MyPrintString().

However that would only change the name of the command, it would not add whatever functionality changes you added to your version of MyPrintString.

But if you create a new include file, let's say “engine_include.nss” and add the following:


You bnow have created a wrapper function around PrintString() by redirecting it to your own function in the include file where you can add your own code.

All you have to do now is to add the #include “engine_include” statement to the top of each file in your module, which can be done easily by using an advanced text editor like EditPlus2, Ultra Edit, vi or a perl script over all .nss files in your nwn\modules\temp0 directory while the module is open in the toolset and saving the module afterwards (I did the same when inserting the spellhooks into every spellscript for the Hordes of the Underdark Expansion). Or, depending on your module setup, you may already have a custom include file included in all your scripts and just including your function redefinition into that file will work without additional steps required.

Once all that is done, you have a private hook into every engine function you have overridden, allowing you to insert debug or profiling code, making functionality changes and do all kinds of other interesting strings with these engine functions.



Caveats

  • Obviously creating an insert macro or running a search and replace over all scripts is still work intensive, so this technique is most useful when creating the ground work for a new module or world in NWN and probably NWN2. Best practice would be to always include one core include file into every script you create in your module, that gives you the flexibility of overriding engine commands at will without additional work in your module, at any time.
  • If you somehow manage to screw up nwscript.nss, all kind of weird things can happen. You will not even be able to trust compiler warnings anymore, as you have damaged the file that tells the compiler how to read your scripts. However, you can always delete your modified nwscript.nss from override to fix your issues.
  • When BioWare adds new scripting functions, we make changes to nwscript.nss. New scripting commands are always added at the bottom of the file (remember - the file order is what defines which engine command it maps to, the position of a command in the file never changes), but sometimes existing functions are extended by new parameters (existing parameters are never changes, as this would break all scripts that people ever created).
  • I told you it's dangerous to mess with this file, so don't blame me if your module goes up in flame, make proper backups instead!


Georg Zoeller dom@gulbsoft.de, 2006-01-31