Impact Script
From NWN1 Custom Content Guide
First Draft... for instance need to add Eligio's Chill Touch example and comments, anyway all this will probably need major revisions with 1.33.
An impact script is just like any other script, but it is executed by the engine each time a capacity implemented through a line in spells.2da is used by an NPC or a PC. Such capacities include [useable feats|Feats], [spells and monster spell-like abilities|Spells], [item cast-spell properties|iprp_spells.2da]. Of course you have to tell the engine which script should be executed when the aforementioned capacity is used. This is done through the [ImpactScript column|spells.2da#ImpactScript] of your line in spells.2da. What we will be looking at here are some of the specific scripting "tools" for such scripts.
Note that all that follows is very much subject to change with HotU, there will be new functions.
!!!#[Native nwscript functions]
!OBJECT_SELF In an impact script, OBJECT_SELF is always the "caster" of the "spell". If the capacity is truly a spell, or a monster spell-like ability that is straightforward: it is the NPC or the PC who cast it. For a feat, it is its user. For an item cast-spell property *it is the user also* not the item.
![int GetSpellID() |http://www.reapers.org/nwn/reference/compiled/function.GetSpellId.html]
The int returned is the index of the spell, feat, etc in [spells.2da]. As usual it is the actual line count that matters, not the value put in the first column (but the two should coincide of course)
A frequent use of this function is for radial spells. To keep your scripts orderly you'll probably want all the subradials to trigger the same impact script. However inside the script, you'll need to differentiate between the different versions at some point.
int nSpell = GetSpellID();
...
switch(nSpell)
{
case POLYMORPH_RABBIT:
...
break;
case ...
}
Notice the use of a named constant. You do not need to alter nwscript.nss for that. Instead use a general include script for your spells where you will define all your constants using the const keyword:
const int POLYMORPH_RABBIT = 3210;
This will make your scripts clearer. Furthermore should you insert a spell on line 3000 of [spells.2da] , and doing so increment the index of all the spell that follows, you'll just need to change the values in your include then recompile the spell scripts, instead of looking through all the scripts for the spells you moved.
There is no drawback to this approach since const "variables" which are not called in a script will not "appear" in the compiled result. Torlack's compiler, nwnnsscomp.exe, (-co mode) can optimise further than the one of Bioware (conditionals with constant clause are suppressed) but that is an other subject.
![object GetSpellTargetObject()|http://www.reapers.org/nwn/reference/compiled/function.GetSpellTargetObject.html] and [object GetSpellTargetLocation()|http://www.reapers.org/nwn/reference/compiled/function.GetSpellTargetLocation.html]
Which one you want to use depends on the value you have put in the [TargetType column of spells.2da|spells.2da#TargetType]. If it includes area/ground you'll want to use ~GetSpellTargetLocation, else ~GetSpellTargetObject (unless of course the value is 0x01, self only, in which case all you need is OBJECT_SELF)
![object GetSpellCastItem()|http://www.reapers.org/nwn/reference/compiled/function.GetSpellCastItem.html]
That will give you the item that was used to cast the "spell". Chances are you'll only use this if you are scripting funky item properties. We will see why bellow. Examples would be: an item that shapechange itself (when activated it turns into an other item with the same cast-spell radial property, some kind of Swiss knife if you wish). That one you can do (kind of) prior to 1.33. With dynamic item properties new things will be possible: an item that grants a feat for limited period of time after being activated for instance (you add a "bonus feat" property for a little while to the item that was used to cast the spell).
In all of Bioware's script I have found only 2 occurrences ( plus one commented out): nw_s3_activated01.nss (the script that implements the ~OnActivateItem event of the module) and x0_s3_portal.nss.
![object GetCasterLevel(object oCreature) |http://www.reapers.org/nwn/reference/compiled/function.GetCasterLevel.html]
GetCasterLevel(OBJECT_SELF) will get you the level of the caster since it returns his level relatively to the last spell he cast. For item cast-spell properties it will be the number you put in the [CasterLvl| iprp_spells.2da#CasterLevel] Column of iprp_spells.2da, so no problem.
As for feats, it will return 0, even if the feat is specific to a class, but in that case there is an easy way around it: just get the level of OBJECT_SELF in that class (e.g. for example nw_s2_bardsong.nss).
1.33/HotU: Georg Zoeller mentioned ~GetLastSpellCastClass being added, this will probably take care of the feat problem. It would probably be an impact script specific function and make this one kind of obsolete. Though I wonder what it will return if a spell is cast from an item.
There is a bug related to that function... One could call it a design issue. When you create an ~AreaOfEffect, the level and save DC are not stored on the effect. In the heartbeat, On Enter and On Exit scripts, the associated function access those data as if the spell was just cast. So if you use a custom usable feat while such a spell (acid fog for instance) is still active, it will set the caster level to 0, and the save DC to 10 for instance. Funnily enough there is no problem with metamagic. Perhaps because feats never change this flag.
The next question is does it affects a simple Wizard/Sorc multiclassed character? The answer is yes, but not exactly in the same way. Two values are saved in some way.
Wizard 8/ sorcerer 1 casts empowered Acid Fog which was made a level 1 wizard spell for the occasion.
on the first heartbeat we get:
caster level 8 (unused in this spell added ~GetCasterLevel(~GetEffectCreator()) to the script, see nw_s0_bladebarc for a spell where Bioware uses it)
metamagic 2
save DC 13
now she casts ray of frost as a sorcerer spell.
next heartbeat of the acid fog is:
caster level : 1, bingo that is the sorcerer level, not the wizard one.
but:
metamagic 2
save DC 13
which are the correct values...
![int GetMetaMagicFeat()|http://www.reapers.org/nwn/reference/compiled/function.GetMetaMagicFeat.html], [int GetSpellSaveDC()|http://www.reapers.org/nwn/reference/compiled/function.GetSpellSaveDC.html] and [ResistSpell(object oCaster, object oTarget)|http://www.reapers.org/nwn/reference/compiled/function.ResistSpell.html]
See above for the comments
Also, wrapper functions [MyResistSpell|http://www.reapers.org/nwn/reference/compiled/function.MyResistSpell] and [MySavingThrow|http://www.reapers.org/nwn/reference/compiled/function.MySavingThrow] are defined in X0_I0_SPELLS.nss/nw_i0_spells.nss.
![int EventSpellCastAt(object oCaster, int nSpell, int bHarmful=TRUE)|http://www.reapers.org/nwn/reference/compiled/function.EventSpellCastAt.html]
You should use this on every creature that is affected by your spell. int nSpell should be the index of your spell in spells.2da, but remember to use named constants. And don't forget to put the FALSE as the last parameter if the spell is benign.
![int DecrementRemainingSpellUses(object oCreature, int nSpell)|http://www.reapers.org/nwn/reference/compiled/function.DecrementRemainingSpellUses.html] In this context this could be used to link the uses-per-day of two spells for example.
![int Get2daString(string s2DA, string sColumn, int nRow)|http://www.reapers.org/nwn/reference/compiled/function.Get2daString.html] This could be useful to retrieve data from the 2das that is not provided by a native function.
![effect related functions|http://www.reapers.org/nwn/reference/compiled/categoryfunction.Effects.html]
Chances are, you will use more than one of them.
Particularly noteworthy are:
[effect EffectPolymorph(int nPolymorphSelection)|http://www.reapers.org/nwn/reference/compiled/function.EffectPolymorph.html]
which you can extend by editing [polymorph.2da]
[effect EffectAreaOfEffect(int nAreaEffectId, string sOnEnterScript="", string sHeartbeatScript="", string sOnExitScript="")|http://www.reapers.org/nwn/reference/compiled/function.EffectAreaOfEffect.html]
which allows you to have On Enter, heartbeat and On Exit scripts in a delimited zone.
see [vfx_persistent.2da] , obviously not needed if you just want to change the scripts.
[effect EffectLinkEffects(effect eChildEffect, effect eParentEffect )|http://www.reapers.org/nwn/reference/compiled/function.EffectLinkEffects.html]
Important so all effects go off simultaneously when spell cease (one way or another). Note that a link can never be constructed from just two visual effects, one part of a link must always be a non visual effect or the link will fail!
Main_Page | 2DA | spells.2da | Impact Script
