Tutorial: Advanced effect tutorial

From NWN Lexicon
Jump to: navigation, search

Introduction

So as was said in previous effect tutorial, effects are just a special constructors/structures with some parameters inside. In vanilla there is not much we can access, but here comes nwnx to grant us more options.

In this tutorial I will explain various advanced techniques with effects using NWNX and how effects actually work in engine.

You will need either nwnx_patch, nwncx_patch (for singleplayer) or nwnx_funcs or nwnx_effects. In this tutorial I will describe functions from nwnx_patch but since they were heavily inspired by nwnx_effects (just using different method to make them) most informations here should apply to nwnx_effects as well.

Before we proceed into custom function description, it is worth to mention that nwnx cannot change already existing and applied effects on creature. It is not possible to loop all effects on creature, pick one and change its values using nwnx functions. This limitation exists because the result of GetFirst/NextEffect function is actually just a copy of the effect (RemoveEffect works with copy because it loops all effects on target and compare them with this copy). Changes into effect will only exists inside the current script. The only way to change already existing effect is to re-apply it after you modify some of its values. Also, to my understanding custom nwnx functions (nwnx_funcs has such) that allows you to modify effects applied on creature doesn't work.

This is because most effects are just "markers". Engine will run OnApply code when effect is applied and OnRemove code when effect is removed. For example, assume we apply ability increase effect to player.

effect eBonus = EffectAbilityIncrease(ABILITY_STRENGTH,4);
ApplyEffectToObject(DURATION_TYPE_PERMANENT,eBonus,oPC);

What happens in engine when you do this is that:
1) Engine runs OnApply event for effect ability increase.
2) this reads the effect values (integers) and add them into creature's engine field with ability bonus of given ability type, this field is read by engine and transfers into what you see in character sheet ie. the green bonus
3) creates icon effect and applies it
4) OnApply event fires for icon effect with value 27 (ABILITY_INCREASE_STR)
5) code of icon effect will show given icon in player's effect list

If the effect is removed, this happens:
1) Engine runs OnRemove event for effect ability increase.
2) this reads the effect values (integers) and substract them from creature's engine field with ability bonus of given ability type
3) code searchs for an icon effect with same id and removes it
4) OnRemove event fire for icon effect
5) code of icon effect will remove the icon from player's effect list

This means that even when is actually possible to change values of existing effect, nothing will change because all the effect bonuses or penalties were already applied in OnApply event and so they remain the same because modifying effect won't trigger OnApply event again. Effect needs to be removed and reapplied to make the changes to manifest.


Special note to nwnx_patch. Unlike nwnx_effects, many functions in nwnx_patch have effect return type. That means that they work similarly to the SupernaturalEffect/ExtraordinaryEffect and thus you need to use them in following way:

//this example changes effect stunned into haste effect
effect eEffect = EffectStunned();
eEffect = NWNXPatch_SetEffectTrueType(eEffect,EFFECT_TRUETYPE_HASTE);

Other plugins usually does this in form of void function instead and redeclare effects is not needed. However the disadvantage of other plugins is that the effect functions works on last declared effect from EffectSomething() or GetFirst/NextEffect. Nwnx_patch doesn't have this limitation.

NWNX functions to deal with effects

First of all. It is important to explain that functions GetFirst/NextEffect are not returning many effects that might be present on creature. NWScript allows us to access basically only effects created in scripts with some exceptions such as crippling strike etc. But there might be much more effects than that. Each itemproperty has its own internal and hidden effect and there are more.

Nwnx_patch functions to access internal effects are:

effect NWNXPatch_GetFirstEffect(object oObject);
effect NWNXPatch_GetNextEffect(object oObject);

Usually there is no need to access internal effects, until you start making your own custom effects or applying effects with DURATION_TYPE_INNATE. This is an example how to loop through all effects on object/player including internal ones:

    effect eEffect = NWNXPatch_GetFirstEffect(oPC);
    while(NWNXPatch_GetEffectTrueType(eEffect) != EFFECT_TRUETYPE_INVALIDEFFECT)
    {
        if(GetEffectSpellId(eEffect) == 12345)
        {
            RemoveEffect(oPC,eEffect);
        }
        eEffect = NWNXPatch_GetNexttEffect(oPC);
    }

Note, the while cycle uses NWNXPatch_GetEffectTrueType(eEffect) != EFFECT_TRUETYPE_INVALIDEFFECT to verify if the effect is valid. This is because GetIsEffectValid vanilla function again intentionally consider internal effects not valid even when they are. So when dealing with internal effects you need to check truetype instead. Invalid effect will have truetype 0.

Also, the limitation explained in introduction still apply.

Next to explain will be truetype. Effect type is in nwnx reffered as truetype. That's because NWScript function GetEffectType uses completely different set of constants and excludes many types from us. Engine knows 92 effect truetypes (+ invalid) and here is their list:

const int EFFECT_TRUETYPE_INVALIDEFFECT                    =  0;
const int EFFECT_TRUETYPE_HASTE                            =  1;
const int EFFECT_TRUETYPE_DAMAGE_RESISTANCE                =  2;
const int EFFECT_TRUETYPE_SLOW                             =  3;
const int EFFECT_TRUETYPE_RESURRECTION                     =  4;
const int EFFECT_TRUETYPE_DISEASE                          =  5;
const int EFFECT_TRUETYPE_SUMMON_CREATURE                  =  6;
const int EFFECT_TRUETYPE_REGENERATE                       =  7;
const int EFFECT_TRUETYPE_SETSTATE                         =  8;
const int EFFECT_TRUETYPE_SETSTATE_INTERNAL                =  9;
const int EFFECT_TRUETYPE_ATTACK_INCREASE                  = 10;
const int EFFECT_TRUETYPE_ATTACK_DECREASE                  = 11;
const int EFFECT_TRUETYPE_DAMAGE_REDUCTION                 = 12;
const int EFFECT_TRUETYPE_DAMAGE_INCREASE                  = 13;
const int EFFECT_TRUETYPE_DAMAGE_DECREASE                  = 14;
const int EFFECT_TRUETYPE_TEMPORARY_HITPOINTS              = 15;
const int EFFECT_TRUETYPE_DAMAGE_IMMUNITY_INCREASE         = 16;
const int EFFECT_TRUETYPE_DAMAGE_IMMUNITY_DECREASE         = 17;
const int EFFECT_TRUETYPE_ENTANGLE                         = 18;
const int EFFECT_TRUETYPE_DEATH                            = 19;
const int EFFECT_TRUETYPE_KNOCKDOWN                        = 20;
const int EFFECT_TRUETYPE_DEAF                             = 21;
const int EFFECT_TRUETYPE_IMMUNITY                         = 22;
const int EFFECT_TRUETYPE_SET_AI_STATE                     = 23;
const int EFFECT_TRUETYPE_ENEMY_ATTACK_BONUS               = 24;
const int EFFECT_TRUETYPE_ARCANE_SPELL_FAILURE             = 25;
const int EFFECT_TRUETYPE_SAVING_THROW_INCREASE            = 26;
const int EFFECT_TRUETYPE_SAVING_THROW_DECREASE            = 27;
const int EFFECT_TRUETYPE_MOVEMENT_SPEED_INCREASE          = 28;
const int EFFECT_TRUETYPE_MOVEMENT_SPEED_DECREASE          = 29;
const int EFFECT_TRUETYPE_VISUALEFFECT                     = 30;
const int EFFECT_TRUETYPE_AREA_OF_EFFECT                   = 31;
const int EFFECT_TRUETYPE_BEAM                             = 32;
const int EFFECT_TRUETYPE_SPELL_RESISTANCE_INCREASE        = 33;
const int EFFECT_TRUETYPE_SPELL_RESISTANCE_DECREASE        = 34;
const int EFFECT_TRUETYPE_POISON                           = 35;
const int EFFECT_TRUETYPE_ABILITY_INCREASE                 = 36;
const int EFFECT_TRUETYPE_ABILITY_DECREASE                 = 37;
const int EFFECT_TRUETYPE_DAMAGE                           = 38;
const int EFFECT_TRUETYPE_HEAL                             = 39;
const int EFFECT_TRUETYPE_LINK                             = 40;
const int EFFECT_TRUETYPE_HASTE_INTERNAL                   = 41;
const int EFFECT_TRUETYPE_SLOW_INTERNAL                    = 42;
const int EFFECT_TRUETYPE_MODIFYNUMATTACKS                 = 44;
const int EFFECT_TRUETYPE_CURSE                            = 45;
const int EFFECT_TRUETYPE_SILENCE                          = 46;
const int EFFECT_TRUETYPE_INVISIBILITY                     = 47;
const int EFFECT_TRUETYPE_AC_INCREASE                      = 48;
const int EFFECT_TRUETYPE_AC_DECREASE                      = 49;
const int EFFECT_TRUETYPE_SPELL_IMMUNITY                   = 50;
const int EFFECT_TRUETYPE_DISPEL_ALL_MAGIC                 = 51;
const int EFFECT_TRUETYPE_DISPEL_BEST_MAGIC                = 52;
const int EFFECT_TRUETYPE_TAUNT                            = 53;
const int EFFECT_TRUETYPE_LIGHT                            = 54;
const int EFFECT_TRUETYPE_SKILL_INCREASE                   = 55;
const int EFFECT_TRUETYPE_SKILL_DECREASE                   = 56;
const int EFFECT_TRUETYPE_HITPOINTCHANGEWHENDYING          = 57;
const int EFFECT_TRUETYPE_SETWALKANIMATION                 = 58;
const int EFFECT_TRUETYPE_LIMIT_MOVEMENT_SPEED             = 59;
const int EFFECT_TRUETYPE_DAMAGE_SHIELD                    = 61;
const int EFFECT_TRUETYPE_POLYMORPH                        = 62;
const int EFFECT_TRUETYPE_SANCTUARY                        = 63;
const int EFFECT_TRUETYPE_TIMESTOP                         = 64;
const int EFFECT_TRUETYPE_SPELL_LEVEL_ABSORPTION           = 65;
const int EFFECT_TRUETYPE_ICON                             = 67;
const int EFFECT_TRUETYPE_RACIAL_TYPE                      = 68;
const int EFFECT_TRUETYPE_VISION                           = 69;
const int EFFECT_TRUETYPE_SEEINVISIBLE                     = 70;
const int EFFECT_TRUETYPE_ULTRAVISION                      = 71;
const int EFFECT_TRUETYPE_TRUESEEING                       = 72;
const int EFFECT_TRUETYPE_BLINDNESS                        = 73;
const int EFFECT_TRUETYPE_DARKNESS                         = 74;
const int EFFECT_TRUETYPE_MISS_CHANCE                      = 75;
const int EFFECT_TRUETYPE_CONCEALMENT                      = 76;
const int EFFECT_TRUETYPE_TURN_RESISTANCE_INCREASE         = 77;
const int EFFECT_TRUETYPE_BONUS_SPELL_OF_LEVEL             = 78;//nonfunctional
const int EFFECT_TRUETYPE_DISAPPEARAPPEAR                  = 79;
const int EFFECT_TRUETYPE_DISAPPEAR                        = 80;
const int EFFECT_TRUETYPE_APPEAR                           = 81;
const int EFFECT_TRUETYPE_NEGATIVE_LEVEL                   = 82;
const int EFFECT_TRUETYPE_BONUS_FEAT                       = 83;
const int EFFECT_TRUETYPE_WOUNDING                         = 84;
const int EFFECT_TRUETYPE_SWARM                            = 85;
const int EFFECT_TRUETYPE_VAMPIRIC_REGENERATION            = 86;
const int EFFECT_TRUETYPE_DISARM                           = 87;
const int EFFECT_TRUETYPE_TURN_RESISTANCE_DECREASE         = 88;
const int EFFECT_TRUETYPE_BLINDNESS_INACTIVE               = 89;
const int EFFECT_TRUETYPE_PETRIFY                          = 90;
const int EFFECT_TRUETYPE_ITEMPROPERTY                     = 91;
const int EFFECT_TRUETYPE_SPELL_FAILURE                    = 92;
const int EFFECT_TRUETYPE_CUTSCENEGHOST                    = 93;
const int EFFECT_TRUETYPE_CUTSCENEIMMOBILE                 = 94;
const int EFFECT_TRUETYPE_DEFENSIVESTANCE                  = 95;

Note EFFECT_TRUETYPE_BONUS_SPELL_OF_LEVEL is not doing anything. The effect can be declared, applied and removed (thus firing effect events) but it will not do anything by default. it seems this effect was an experiment or early way of doing bonus spell slots but later Bioware abadoned it. In 1.69 its empty effect that can be reused for our own purposes.

Nwnx_patch functions to access/modify truetype are:

int NWNXPatch_GetEffectTrueType(effect eEffect);
effect NWNXPatch_SetEffectTrueType(effect eEffect, int nTrueType);

NWNXPatch_GetEffectTrueType allows to access effect truetype. This is usefull when dealing with effect types that are excluded from GetEffectType such as knockdown and taunt. Afaik those are the only two types that we can access in vanilla with GetFirst/NextEffect but that shows as EFFECT_TYPE_INVALIDEFFECT. Another usage is when dealing with internal effects to use in cycles - already explained above.

NWNXPatch_SetEffectTrueType is more interesting. Changing truetype is especially usefull when you want to make an effect that cannot be made in vanilla. The above list od truetypes shows that we can create icon effect. There are two ways to do this, the example code will try to make an effect icon of invulnerability (value 6 - defined in effecticons.2da).

Method 1: Using vanilla effect without parameters and filling the parameters manually

    effect eTemp = EffectStunned();
    effect eIcon = NWNXPatch_SetEffectTrueType(eTemp,EFFECT_TRUETYPE_ICON);
    eIcon = NWNXPatch_SetEffectInteger(eIcon,0,6);

Method 2: Using vanilla effect with same ammount of parameters to pre-fill the parameter what we need

    effect eTemp = EffectHeal(6);
    effect eIcon = NWNXPatch_SetEffectTrueType(eTemp,EFFECT_TRUETYPE_ICON);

Both method will basically create effect EffectIcon(6).

The previous example used function to alter effect integers. Each effect has several values/parameters, they are reffered as integers in nwnx. Usually vanilla effect functions sets first parameter to be 0th integer, second parameter to be 1st, third parameter to be 2nd and so on but there can be exceptions to this rule. Another thing is that VersusRacialType and VersusAlignment functions can add 3 extra integers to the effect, usually on positions 2,3,4 but sometimes 3,4,5 or other depending on effect. It is not documented yet how many integers and what the values are for non-vanilla effect truetypes. It would be worthwhile to document each effect in lexicon or wiki. From my own tests, I know only that:

EFFECT_TRUETYPE_ICON - integer0 - line in effecticons.2da, no other integers
Special note to icon effect: vanilla nwn client can show only icons with id 1-130. This means there is just one slot for custom content and icons on lines 131+ will not show. Client needs to run nwncx_patch to workaround this limitation. Some advanced persistent worlds might have their own nwn launcher to workaround this limitation.
EFFECT_TRUETYPE_TAUNT - integer0 - value of ac penalty or 0 if there shouldn't be any penalty, no other integers
EFFECT_TRUETYPE_DISARM - no integers
EFFECT_TRUETYPE_DAMAGE - Vanilla effect, but nwnx allows to make it a multi damage type which isn't normally possible. Has 13 integers (0-12) that refer to specific damage type. Integers are: 0 bludgeoning, 1 piercing, 2 slashing 3 magical 4 acid 5 cold 6 divine 7 electrical 8 fire 9 negative 10 positive 11 sonic 12 base damage. Additionally there are at least 6 other integers that controlls how the effect will behave. 13 - 0/1 unknown purpose, engine uses with 1 for melee weapon damage, 14 - unknown seems some delay value, EffectDamage will set it to 1000 by default, 15 - used by EffectDamage value of nDamageType, 16 - used by EffectDamage value of nDamagePower, nonfunctional in vanilla due to the bug (nwnx_patch fixes this though), 17 - value 1 indicates the damage effect comes from weapon, this completely changes behavior of the damage effect, if this integer is 1 damage immunities nor resistances apply, also damage shields can fire on effect creator, 18 - value 1 indicates the effect comes from ranged weapon and thus engine won't fire damage shields.

Nwnx_patch functions to access/modify effect integers are:

int NWNXPatch_GetEffectInteger(effect eEffect, int nTh);
effect NWNXPatch_SetEffectInteger(effect eEffect, int nTh, int nValue);

Following example creates a multi-type damage effect dealing 1 point of damage by each type.

    effect eDamage = EffectDamage(1,DAMAGE_TYPE_BLUDGEONING);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,1,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,2,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,3,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,4,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,5,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,6,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,7,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,8,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,9,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,10,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,11,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,12,1);
    eDamage = NWNXPatch_SetEffectInteger(eDamage,17,1);//this is needed to make multitype damage work properly
    eDamage = NWNXPatch_SetEffectInteger(eDamage,18,1);//this is to avoid damage shields to fire against effect creator
    ApplyEffectToObject(DURATION_TYPE_INSTANT,eDamage,oPC);

If you want to modify values of other effects you need to make your own research. Feel free to edit tutorial (or write comment) with another findings.

The rest nwnx functions are self-exemplanatory:

//Returns effect remaining duration.
float NWNXPatch_GetEffectRemainingDuration(effect eEffect);

This can be used to re-apply already existing effect. This function will return the remaining duration of already existing effect on creature, so if you would want to change that effect in any way, you can remove it and re-apply with duration from this function. (The remaining duration will be 0.0 if the effect is permanent).

//Changes the effect caster level. This only affects dispelling.
effect NWNXPatch_SetEffectCasterLevel(effect eEffect, int nCasterLevel);

This function has effect only on dispelling checks. Special note to default effect caster level: in vanilla, prestige classes doesn't affect caster level only spell progression, therefore effect's caster level is set to level of GetLastSpellClass(), if any. Nwnx_patch changes this and sets the caster level to the value calculated from community patch spell engine overrides/modifiers and will automatically count also with prestige classes increasing spellcasting progression.

//Changes the effect creator
effect NWNXPatch_SetEffectCreator(effect eEffect, object oCreator);

This function can be used to change the effect creator (which is OBJECT_SELF in the moment of declaration) without need to use AssignCommand and other workarounds.

//Changes the effect spell id
effect NWNXPatch_SetEffectSpellId(effect eEffect, int nSpellId);

This function is very useful for custom content purposes. Effects created outside spellscript will have its spell id equal to -1. This limitation is reason why builders needs to use different creators for applying effects (to identify where does effect come from). But with possibility to set spell id to any value this is no longer a problem. You can set your effect's spell id to certain value you reserve and then you will always know this effect comes from your custom scripting solution to code custom passive feats. This is especially usefull for creating custom effects.

Nwnx_patch also adds two new duration types to be used with ApplyEffectToObject:

const int DURATION_TYPE_EQUIPPED = 3;
const int DURATION_TYPE_INNATE = 4;

These extra duration types can be only used with ApplyEffectToObject. They won't work on location.

Both duration types makes the effect internal. That practically means invisible to vanilla functions and unremovable too: undispelable by any means, persisting after death, rest. Also, effect will not show icon (except effect icon itself). However, it seems that engine will delete all EQUIPPED and INNATE effects at OnClientEnter event, so you might need to re-apply them.

Duration type DURATION_TYPE_INNATE doesn't offer anything more. Usage of this duration type is when making custom effects. This will be explained below.

Effect applied as DURATION_TYPE_EQUIPPED is also automatically removed if the creator is item that gets unequipped. That makes it perfect solution for making custom itemproperties. Don't forget to set creator to be item by nwnx function or AssignCommand workaround to make this work properly. However this doesn't mean the creator must be item, it can be any object.

Last custom function is:

//This version differs from vanilla function in that it will be able to apply effect on dead target as well
void NWNXPatch_ForceApplyEffectToObject(int nDurationType, effect eEffect, object oTarget, float fDuration=0.0f);

Self-exemplanatory.

Custom effects

Nwnx_patch and nwnx_effects allows to create a custom effect. Effects with truetype above 95 are considered custom effects. Note, that nwnx plugins might use some ids above 95 for their own custom effects, this is case of nwnx_patch where 96 is reserved for custom effect EFFECT_TRUETYPE_NWNXPATCH_MODIFYBAB. It is recommended to start your custom effects from id 100 and above to avoid future conflicts.

You can use 2 custom effects already built in nwnx_patch:

//Create effect which will modify/replace pre-epic BAB
//nBaseAttackBonus - 1-20 althought values above 20 are allowed its untested
effect NWNXPatch_EffectModifyBAB(int nBaseAttackBonus);

This custom effect modifies pre-epic base attack bonus. Unlike vanilla function SetBaseAttackBonus that just sets base number of attacks, this effect will show new BAB value in character sheet too. This is usefull to implement PnP Divine Power or Tenser's Transformation.

//Create an Attack Increase effect that will ignore +20 cap. Currently this function doesn't support
//nModifierType or Versus* functions.
effect NWNXPatch_EffectAttackIncreaseUncapped(int nBonus);

This custom effect allows you to apply attak bonus that won't count towards +20 AB cap. That's all.

Now you probably ask what is this usefull for. Ok, imagine we want to make custom spell that will temporarily grant immortality. We can use vanilla function SetImmortal but here is the catch. We don't know when the effect will be removed. The effect can be removed either by losing its duration, spell re-casting, resting, dispelling or by custom scripts such as antimagic sphere and possibly other cases and we don't know when it will happen. One way would be hooking all these situations and run a special script in these cases - this won't be perfect though (and there are issues like relog too). Or you can make custom effect.

Another reason to make custom effect is to give your effect custom icon. For example, let's say we want to make EffectFrozen. Effect frozen would probably consist of EffectCutsceneParalysis, EffectVisualEffect and EffectIcon. One option is to add icon effect into link with other effects in spellscript or OnHitSpell script. That might not look perfectly if the effectss you want to use in link would show their own icons (not this case though). Making this to be a custom effect can achieve more professional look.

Custom effect will fire effect event script when applied and also when removed. In case of multiplayer it works this way: when character logs out nothing happens, however when character logs in, all effects he had last session will be applied (because technically he logs in without them) and if those effects ran out of duration then they will be removed immediately after application. This means that there is no risk of relogging player keeping the effect benefits when effect duration ran out during he is logged off.

For nwnx_patch, the event script name is 70_mod_effects. If you use community patch you can open this script from core resources, if you downloaded nwnx_patch standalone, script should be included within archive.

The script 70_mod_effect has given structure. Script fires for both OnApply and OnRemove. Script comes with one small example that adds icon into EffectModifyBAB custom effect. (The actual effect is however coded in nwnx)

You should use this structure and only add new lines for your own effects.

Now, how. First, all effects you need to use should be applied as DURATION_TYPE_INNATE. This is for hiding them from custom scripts and avoid losing them. Icon effect will show icon regardless of being innate.

Then the basic logic is:

OnApply:
1) recalculate all the effects of this type applied and add this effect values (if it makes sense - case of custom effect applying +1 charisma for example)
2) remove previous vanilla effects if the old value != new value (if it makes sense)
3) apply new vanilla effects or functions to provide effect's benefits, grant effects specific spell id to be able to retrieve them later

OnRemove:
1) recalculate all the effects of this type applied and substradt this effect values (if it makes sense - case of custom effect applying +1 charisma for example)
2) remove previous vanilla effects if the old value != new value (if it makes sense)
3) if above doesn't make sense, check if any other effect of this type exists on object and if not, remove this effect benefits added by any means

Now, specific examples:

Example 1: EffectImmortal - id 100

//OnApply:
        else if(nEffectTrueType == 100)//EffectImmortal
        {
            if(GetObjectType(OBJECT_SELF) != OBJECT_TYPE_CREATURE) return;
            int bFound = FALSE;
            effect eSearch = GetFirstEffect(OBJECT_SELF);
            while(GetIsEffectValid(eSearch))
            {
                if(eSearch != eEffect && NWNXPatch_GetEffectTrueType(eSearch) == 100)
                {
                    bFound = TRUE;
                    break;
                }
                eSearch = GetNextEffect(OBJECT_SELF);
            }
            if(!bFound)
            {
                SetImmortal(OBJECT_SELF,TRUE);
                effect eIcon = EffectHeal(6);//INVULNERABLE icon
                eIcon = NWNXPatch_SetEffectTrueType(eIcon,EFFECT_TRUETYPE_ICON);
                eIcon = NWNXPatch_SetEffectSpellId(eIcon,10100);//to identify this effect comes from
                ApplyEffectToObject(DURATION_TYPE_INNATE,eIcon,OBJECT_SELF);
            }
        }
 
//OnRemove:
        else if(nEffectTrueType == 100)//EffectImmortal
        {
            if(GetObjectType(OBJECT_SELF) != OBJECT_TYPE_CREATURE) return;
            int bFound = FALSE;
            effect eSearch = GetFirstEffect(OBJECT_SELF);
            while(GetIsEffectValid(eSearch))
            {
                if(eSearch != eEffect && NWNXPatch_GetEffectTrueType(eSearch) == 100)
                {
                    bFound = TRUE;
                    break;
                }
                eSearch = GetNextEffect(OBJECT_SELF);
            }
            if(!bFound)
            {
                SetImmortal(OBJECT_SELF,FALSE);
                eSearch = NWNXPatch_GetFirstEffect(OBJECT_SELF);
                int nType = NWNXPatch_GetEffectTrueType(eSearch);
                while(nType != 0)
                {
                    if(nType == EFFECT_TRUETYPE_ICON && GetEffectSpellId(eSearch) == 10100)
                    {
                        RemoveEffect(OBJECT_SELF,eSearch);
                    }
                    eSearch = NWNXPatch_GetNextEffect(OBJECT_SELF);
                    nType = NWNXPatch_GetEffectTrueType(eSearch);
                }
            }
        }

Example 2: EffectFrozen - id 101

//OnApply
        else if(nEffectTrueType == 101)//EffectFrozen
        {
            if(GetObjectType(OBJECT_SELF) != OBJECT_TYPE_CREATURE) return;
            int bFound = FALSE;
            effect eSearch = GetFirstEffect(OBJECT_SELF);
            while(GetIsEffectValid(eSearch))
            {
                if(eSearch != eEffect && NWNXPatch_GetEffectTrueType(eSearch) == 101)
                {
                    bFound = TRUE;
                    break;
                }
                eSearch = GetNextEffect(OBJECT_SELF);
            }
            if(!bFound)
            {
                effect eParal = EffectCutsceneParalyze();
                effect eVis = EffectVisualEffect(VFX_DUR_ICESKIN);
                effect eDur = EffectVisualEffect(VFX_DUR_FREEZE_ANIMATION);
                effect eIcon = EffectHeal(26);//SLOW icon (since frozen icon doesn't exist)
                eIcon = NWNXPatch_SetEffectTrueType(eIcon,EFFECT_TRUETYPE_ICON);
                effect eLink = EffectLinkEffects(eParal,eVis);
                eLink = EffectLinkEffects(eLink,eDur);
                eLink = EffectLinkEffects(eLink,eIcon);
                eLink = NWNXPatch_SetEffectSpellId(eLink,10101);//to identify this effect comes from
                ApplyEffectToObject(DURATION_TYPE_INNATE,eLink,OBJECT_SELF);
            }
        }
//OnRemove
        else if(nEffectTrueType == 101)//EffectFrozen
        {
            if(GetObjectType(OBJECT_SELF) != OBJECT_TYPE_CREATURE) return;
            int bFound = FALSE;
            effect eSearch = GetFirstEffect(OBJECT_SELF);
            while(GetIsEffectValid(eSearch))
            {
                if(eSearch != eEffect && NWNXPatch_GetEffectTrueType(eSearch) == 101)
                {
                    bFound = TRUE;
                    break;
                }
                eSearch = GetNextEffect(OBJECT_SELF);
            }
            if(!bFound)
            {
                eSearch = NWNXPatch_GetFirstEffect(OBJECT_SELF);
                int nType = NWNXPatch_GetEffectTrueType(eSearch);
                while(nType != 0)
                {
                    if(nType == EFFECT_TRUETYPE_ICON && GetEffectSpellId(eSearch) == 10101)
                    {
                        RemoveEffect(OBJECT_SELF,eSearch);
                    }
                    eSearch = NWNXPatch_GetNextEffect(OBJECT_SELF);
                    nType = NWNXPatch_GetEffectTrueType(eSearch);
                }
            }
        }

Altering vanilla effects

Warning: by the time I write this tutorial, following feature is not yet released in nwnx_patch, this will work in v1.33.

There is one extra feature. Nwnx_patch and nwnx_effects allows you to specify vanilla effect which will fire effect event script on both apply and remove.

This can be used in two ways.

1) Extra benefits when vanilla effect is applied (example: implementing AB or AC bonus from ability bonus/penalty)

2) Completely altering the vanilla effect.

To do this. First you need to change effects.2da, locate line with effect you want to catch and set RunEvent column to 1. This will ensure vanilla effect will trigger 70_mod_effects. If you want to fire the event also for npcs, change column PCOnly to 0 (Warning: might exponentially increase the frequency of the event running! Use with care.)

Now, add code into 70_mod_effects.

Example: hooking EffectAbilityIncrease/Decrease to grant AC bonus from charisma modifier

//custom function code
void RecalculateAC()
{
    int oldMod = GetLocalInt(OBJECT_SELF,"CharismaModifier");
    int nMod = GetAbilityModifier(ABILITY_CHARISMA);
    if(oldMod != nMod)
    {
        SetLocalInt(OBJECT_SELF,"CharismaModifier",nMod);
        NWNXPatch_SetBaseAC(OBJECT_SELF,AC_NATURAL_BONUS,nMod);
    }
}
 
//OnApply
        else if(nEffectTrueType == EFFECT_TRUETYPE_ABILITY_INCREASE || nEffectTrueType == EFFECT_TRUETYPE_ABILITY_DECREASE)
        {
            if(NWNXPatch_GetEffectInteger(eEffect,0) == ABILITY_CHARISMA)
            {
                DelayCommand(0.1,RecalculateAC());
            }
        }
 
//OnRemove
        else if(nEffectTrueType == EFFECT_TRUETYPE_ABILITY_INCREASE || nEffectTrueType == EFFECT_TRUETYPE_ABILITY_DECREASE)
        {
            if(NWNXPatch_GetEffectInteger(eEffect,0) == ABILITY_CHARISMA)
            {
                DelayCommand(0.1,RecalculateAC());
            }
        }

This will grant (uncapped) natural AC bonus for charisma modifier. This however won't grant AC from base charisma modifier so this needs to be paired with a similar code used in OnEnter in case player doesn't have any item granting ability bonus/penalty.

Note, that the delay is here because the event fires before the effect is actually applied or removed. This could probably be coded differently, but since there is the +12 cap issue I decided for an easier way of using delay. The delay is not noticeable anyway.

In case you want completely rescript the vanilla effect (for example granting PnP haste benefits), you need to tell nwnx to bypass original effect code to run. To do this use function BypassEvent(); or use SetLocalInt(OBJECT_SELF,"EFFECT_EVENT_BYPASS",1);