There is no generic spellscript template. There are commonalities like
if (!X2PreSpellCastCode())
return;
which is used primarily to stop the spell when crafting on a workbench.
All scripts have an entity called OBJECT_SELF
which is an ingame object
that ‘owns’ a script as it executes.
object
is an NwScript ‘type’ – like int
and string
etc.
Spellscripts will have a line like this →
SignalEvent(oTarget, EventSpellCastAt(OBJECT_SELF, SPELL_FIREBALL));
which forces a target’s OnSpellCastAt event to fire. Usually it means nothing but it does allow a module-designer to have objects react with custom behaviors to (certain) spells (or to spells generally).
This means that the spell’s potency is capped at level 10 →
int nCasterLvl = GetCasterLevel(oCaster);
if (nCasterLvl > 10)
nCasterLvl = 10;
And this will be used to modify its effects based on what metamagic was used →
int nMetaMagic = GetMetaMagicFeat();
If a spell is an AoE it will have a loop of some sort (there are many different ways to loop over targets) →
object oTarget = GetFirstObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_HUGE, lTarget, TRUE, OBJECT_TYPE_CREATURE | OBJECT_TYPE_DOOR | OBJECT_TYPE_PLACEABLE);
while (GetIsObjectValid(oTarget))
{
// apply efffect(s) here, then find next target ->
oTarget = GetNextObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_HUGE, lTarget, TRUE, OBJECT_TYPE_CREATURE | OBJECT_TYPE_DOOR | OBJECT_TYPE_PLACEABLE);
}
but if a spell is single-target only there won’t be a loop.
Some spells are cast at a location on the ground →
location lTarget = GetSpellTargetLocation();
but others are cast on an object →
object oTarget = GetSpellTargetObject();
A visual effect might be applied to a target →
effect eVis = EffectVisualEffect(VFX_IMP_FROST_S);
VFX_IMP_FROST_S
is an NwScript constant (see NwScript.nss) that references VisualEffects.2da
sometimes they are applied before any Resist/Save, sometimes only if those fail. It is applied with code like this →
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oTarget);
After damage (if it is a damaging spell) is calculated it’s applied with similar code. The duration-type can be DURATION_TYPE_INSTANT
, DURATION_TYPE_TEMPORARY
, or DURATION_TYPE_PERMANENT
(again, those are NwScript constants).
Delays can be used to give some visual realism.
If a spell can be resisted, it will have code like so (before applying negative effects) →
if (!MyResistSpell(OBJECT_SELF, oTarget, fDelay))
{
// do bad stuff
}
MyResistSpell()
is a function in the #include file nw_i0_spells that applies a visual effect based on whether a target has a Globe of Invulnerability, a Spell Mantle, or has resisted naturally.
Since Fireball uses reflex-adjusted damage, it uses this for a saving throw →
nDamage = GetReflexAdjustedDamage(nDamage, oTarget, GetSpellSaveDC(), SAVING_THROW_TYPE_COLD);
SAVING_THROW_TYPE_COLD
is another NwScript constant.
GetSpellSaveDC()
is a hardcoded function that automatically calculates DC. It’s not always accurate, particularly when using add-ons like the kPrC Pack.
For non-reflex-adjusted spells, that require a Save, the function MySavingThrow()
is typically used (also in the #include file nw_i0_spells). It handles the fact that in Nwn2 in order to account for Immunity from an effect-type, the effect should be applied and hardcode will deal with it (rather than it being dealt with in the script itself – there are spells that use alternate Save handling since they need to do further calculations that are independent of the return from the stock MySavingThrow()
– eg. spells that need to check more than one type of bad-effect).
if (spellsIsTarget(oTarget, SPELL_TARGET_STANDARDHOSTILE, OBJECT_SELF))
{
// target ok
}
spellsIsTarget()
is also in the #include file nw_i0_spells. It’s a complicated function that determines if a target is valid given a targeting-type: SPELL_TARGET_STANDARDHOSTILE
, SPELL_TARGET_SELECTIVEHOSTILE
, or SPELL_TARGET_ALLALLIES
. StandardHostile is based on the difficulty that player has set in Options (can affect allies). SelectiveHostile is hostile only. AllAllies is (roughly) allies.
GetSpellId()
is the row-id in Spells.2da
EffectLinkEffects()
can be used to link any number of effects together so that they all get removed if a spell is dispelled. It is widely used, just not in the Fireball script.
A custom spell-id can be set for effects with SetEffectSpellId()
– if not set, the spell-id will be the row-id in Spells.2da. A custom spell-id can be used to later remove the specific effects from target(s) without a risk of accidentally removing other effects.
Feats are often called ‘spellabilities’ – because they are based on a spellscript (there is a reference in Feat.2da to the corresponding spell in Spells.2da). Other feats, such as a feat that enhances a spell, are merely a feat that can be checked for in a spellscript to apply an enhancement – ie. casting the spell is not dependent on having the Feat.
for example →
if (GetHasFeat(FEAT_ENHANCED_FIREBALL))
{
nCasterLvl = nCasterLvl + 5;
}
where FEAT_ENHANCED_FIREBALL
is a row-id in Feat.2da that has been added and defined (custom).
See Reserved 2da ranges.
Creating a custom Class is nontrivial. Eg Artisan PrC
We also have access to things like the spellhook that fires before any spellcast (eg. can stop spells when in an anti-magic area). Also, some spellabilities will call the X2PreSpellCastCode()
although technically they shouldn’t because they’re not spells.
etc
ps. Some spells will check for and remove other spell-effects of similar type … eg. GreaterInvisibility should remove Invisibility. But that’s a nicety since the engine usually handles it ok
good luck (I may be indisposed for a while)