NWN2Wiki
Advertisement
Is symbolpain
Symbol of Pain
Spell Information
Spell level : Innate level: 5, Cleric: 5, Sorcerer/Wizard: 5
School : Necromancy
Descriptor(s) : Evil
Components : Verbal and Somatic
Range : Short (AoE), See text
Target/Area : Trap (AoE), See text
Duration : 1 min. * cLevel
10 Rounds(cLevel)
Save : Fortitude negates
Spell resistance : Yes

Description

This spell allows you to scribe a potent rune of power upon a surface. When triggered, a symbol of pain imposes a -4 penalty on attack rolls, skill checks, and ability checks to all enemies in the area (60 feet radius). These effects last for 1 hour.

Note: Magic traps such as symbol of pain are hard to detect and disable. A rogue (only) can use the Search skill to find a symbol of pain and Disable Device to thwart it (DC30).

Notes

  • All symbol spells have been cut from the game. However, they still exist in the toolset and can be restored by activating them through spells.2da. See Bug section below for details.
  • This spell is superior to Bestow Curse, as it will reduce all abilities by 4, reducing the enemies' saving throws in the process. Also, it affects all enemies in a vast area (60 feet), not just a single target.
  • This trap is not destroyed once triggered. It stays in place for its entire duration (1 turn / caster level) unless disarmed or dispelled. Every time an enemy triggers it, it releases another burst.
  • It only ever affects enemies, even on hardcore difficulty.
  • The trap cannot be recovered, only disarmed or dispelled. The Dispel chance is same as for AoEs (see Dispel Magic for details) or 100% if the creator of the symbol casts Dispel herself.
  • The penalties do not stack if a target is already affected by this rune. However, they do stack with Bestow Curse and other spells that apply ability damage.

Bugs

nx2_s0_symbol_of_pain, ginc_symbol_spells, nx2_s0_symbol_of_paina:

All symbols scripts including Symbol of Pain are extremely inefficient. They are based on an interaction of a trap object and an AoE object that are both created when a rune spell is cast. The trap object is actually just for show and does absolutely nothing. The spell functions similar to a Glyph of Warding, which is triggered by entering the AoE and it is the AoE object, not the trap object that sends out the burst and applies the penalties. It would make more sense to get rid of the AoE altogether and to rely on the trap object itself. Below are a couple of hints that should give potential modders an idea on how to create such a trap:

1) Creating event handlers for the trap (ginc_symbol_spells.nss):

SetEventHandler(oTrap, SCRIPT_TRIGGER_ON_HEARTBEAT, "nx2_b_symbol_hb");

SetEventHandler(oTrap, SCRIPT_TRIGGER_ON_TRAPTRIGGERED, "nx2_b_symbol_triggered");

SetEventHandler(oTrap, SCRIPT_TRIGGER_ON_USER_DEFINED_EVENT, "nx2_b_symbol_triggered");

2) Making the trap object remember who created it, the SpellSaveDC of the creator and the location of the trap (due to a hard-coded bug, fails to detect its own location when triggered!)-ginc_symbol_spells.nss:

SetLocalInt(oTrap,"SpellSaveDC", GetSpellSaveDC());

SetLocalObject(oTrap, "oCreator", OBJECT_SELF);

SetLocalLocation(oTrap,"Location",lTarget);



3) Triggering the trap when it's created (the OnEnter event works differently for traps than for AoEs! When an AoE spell is cast on a creature, the OnEnter event is triggered, see Mind Fog or Cloudkill, but the trap OnEnter event is only triggered when an object actually MOVES around while standing on the trap or actually enters the trap) - ginc_symbol_spells.nss:

SignalEvent(oTrap,EventUserDefined(0));

4) The triggering script (nx2_b_symbol_triggered) needs to check if GetEnteringObject is valid, if it is, it's a real OnEnter event, if not, it's the artificially created OnEnter event that is created when the trap object is created (see above):

if(!GetIsObjectValid(GetEnteringObject())) //if GetEnteringObject is not valid, check for GetFirstInPersistentObject-GetNextInPersistentObject, if any enemies are currently standing on the trap, pass the execution to nx2_s0_symbol_of_paina.nss that triggers the burst and applies the penalties:

{

oTemp=GetFirstInPersistentObject(OBJECT_SELF,OBJECT_TYPE_CREATURE);

while(GetIsObjectValid(oTemp) && bShouldTrigger == FALSE)

{

if(GetIsEnemy(oTemp, oCreator))

{

bShouldTrigger = TRUE;

}

else //currently checked target is not an enemy, check for next creature standing on the trap:

{

oTemp = GetNextInPersistentObject(OBJECT_SELF,OBJECT_TYPE_CREATURE);

}

}

} else //GetEnteringObject is valid, no need to check for enemies standing on the trap, just check if the entering object is an enemy, if yes, indicate that the trap should trigger:

{

oTemp = GetEnteringObject();

if(GetIsEnemy(oTemp, oCreator))

{ bShouldTrigger = TRUE;

}

}

string sSpellLocal = GetLocalString(OBJECT_SELF, "Symbol_Type");

if(bShouldTrigger && !GetLocalInt(oTemp, sSpellLocal))

{

SetLocalInt(oTemp, sSpellLocal, TRUE); //this is a "bAlreadyAffected" check to prevent re-triggers within milliseconds

DelayCommand(0.3, DeleteLocalInt(oTemp, sSpellLocal));

ExecuteScript(sTriggered, OBJECT_SELF);

}

}

5) Deleting the trap:

The trap duration is limited to 1 turn per caster level. So how does one check if it's time for the trap to expire? There are three possibilities:

a) Apply any useless effect like Ultravision to the trap for the duration and use the trap's hearbeat script (nx2_b_symbol_hb.nss) to check if the effect has expired. If true, tell the trap to disarm itself. Does not work if the player saves the game after creating the trap (some kind of hard-coded bug)

b) Set the creation time as a localInt on the trap object and then use the heartbeat script to see if it's time to disarm itself

c) Check if player is resting - "GetIsResting" (resting forwards the clock by 8 hours). If true, disarm.

6) Creating the burst when triggered and applying the penalties (nx2_s0_symbol_of_paina.nss):

object oCaster = GetLocalObject(OBJECT_SELF,"oCreator");

float fDuration = HoursToSeconds(1);

int nSaveDC=GetLocalInt(OBJECT_SELF, "SpellSaveDC");

location lTarget = GetLocalLocation(OBJECT_SELF,"Location");

effect eActivated = EffectVisualEffect( VFX_AOE_SPELL_BLOOD_TO_WATER );

effect eVisual = EffectVisualEffect(VFX_HIT_SPELL_BLOOD_TO_WATER);

....//original code

eLink = SetEffectSpellId(eLink, SPELL_SYMBOL_OF_PAIN); // no stacking

ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eActivated,lTarget);

//validity & hostility handled by nx2_b_symbol_triggered, so here just assume entering object is a valid and hostile object

oTarget = GetFirstObjectInShape(SHAPE_SPHERE, RADIUS_SIZE_VAST, lTarget,FALSE,OBJECT_TYPE_CREATURE);

while(GetIsObjectValid(oTarget))

{

if ( GetIsEnemy(oTarget, oCaster) && !GetIsDead(oTarget) )

{

SignalEvent(oTarget, EventSpellCastAt(oCaster, SPELL_SYMBOL_OF_PAIN, TRUE));

if ( !GetHasSpellEffect(SPELL_SYMBOL_OF_PAIN, oTarget) )

{

//MyResistSpell and MySavingThrow (SAVING_THROW_TYPE_EVIL) checks follow, if failed, apply the penalties and then move to GetNextObjectInShape



7) Altering ALL dispel spells to include code to dispel Symbol spells (Note: Dispel spells cannot dispel traps, only AoEs!) This involves changes to the following scripts: nw_s0_lsdispel.NSS, nw_s0_dismagic.NSS, nw_s0_grdispel.NSS, nw_s0_morddisj.NSS as well as warlock dispel invocations. The DC for dispel should roughly follow the standard DC for dispelling AoEs:

int nCnt = 1;

object oCreator;

int CasterRoll;

int DispelDC; object oTrap= GetNearestObjectToLocation(OBJECT_TYPE_TRIGGER, lLocal,nCnt);

while( GetIsObjectValid(oTrap) )

{

if( GetEffectType(GetFirstEffect(oTrap))==EFFECT_TYPE_ULTRAVISION ) //see notes above why ultravision

{

CasterRoll=d20()+nCasterLevel;

oCreator=GetLocalObject(oTrap,"oCreator");

DispelDC = 11+ GetCasterLevel(oCreator);

if (OBJECT_SELF==oCreator)//100% chance to dispel your own symbol

{

FloatingTextStrRefOnCreature(100929,OBJECT_SELF); // "AoE dispelled"

SetTrapDisabled(oTrap);

}

else if (CasterRoll>=DispelDC)

{

SendMessageToPC(GetFirstPC(FALSE), IntToString(CasterRoll) + " vs. " + IntToString(DispelDC) );

FloatingTextStrRefOnCreature(100929,OBJECT_SELF); // "AoE dispelled"

SetTrapDisabled(oTrap); }

} nCnt++;

oTrap= GetNearestObjectToLocation(OBJECT_TYPE_TRIGGER, lLocal,nCnt);

}

Advertisement