Tutorial: Effects, their applications, remarks and secrets
Introduction
In this tutorial I will explain how effect works, how effect can be created, removed and all the mechanic behind engine.
This tutorial will only cover the vanilla NWN, using NWNX you can have full controll of effects and new methods to create them, but that would be for different tutorial, so I will focus only what one can do in vanilla.
So, what is the effect?
Effect is a constructor: a special data structure that has several fields/members that are accessable via various functions. Also, every effect has an ID. This ID is not accessable in vanilla NWN but we know when the effect has the same ID and when not.
One thing, that should be probably said before we go into deep is, that effects of course doesn't have to be declared. In a few situations (that I will explain in article) it would be also counterproductive.
There is one special effect and thats a null effect with an ID of 0 or -1 (not really sure but thats redundant), you can get such effect by declaring an effect without value:
So at this moment this effect is null. If you would loop through all effect you could use if(eLoop != eNull) with same result as with if(GetIsEffectValid(eLoop)) (Hint: and it would be more efficient too).
The other way is to declare effect with a value:
effect eHaste = EffectHaste();
This will create a new effect with an unique ID. If you declare a two effects with a same value they will not be equal because their ID differs. See the following NWScript example:
effect eNull2;
effect eHaste = EffectHaste();
effect eHaste2 = EffectHaste();
SendMessageToPC(GetFirstPC(),"eNull == eNull2 => "+IntToString(eNull == eNull2));
SendMessageToPC(GetFirstPC(),"eHaste == eHaste2 => "+IntToString(eHaste == eHaste2));
//The messages printed to PC are: "eNull == eNull2 => 1(TRUE)" and "eHaste == eHaste2 => 0(FALSE)".
Now when ID does NOT changes. if you declare an effect with a value of already existing effect, ID will be equal:
effect eHaste2 = eHaste;
//In this case if we compared both effect together, we would get TRUE.
Also, the effect ID doesn't change after changing the effect subtype using functions: MagicalEffect, ExtraordinaryEffect and SupernaturalEffect.
effect eHaste2 = ExtraordinaryEffect(eHaste);
///In this case, an effect ID is equal too.
In the case of EffectLinkEffects(), the ID changes every time.
effect eUV = EffectUltravision();
effect eSee = EffectSeeInvisible();
effect eLink = EffectLinkEffects(eHaste,eUV);
eLink = EffectLinkEffects(eLink,eSee);
///each effect there has unique id
Each effect has also several parameters, only few are however accessable within vanilla NWScript though:
- Type : accessable by GetEffectType() : type of the effect, refers to the EFFECT_TYPE_* constant
- SubType : accessable by GetEffectSubType() : subtype of the effect, refers to the SUBTYPE_* constant
- Creator : accessable by GetEffectCreator() : object that initialized this effect, aka OBJECT_SELF in the script, where this effect was declared.
- SpellId : accessable by GetEffectSpellId() : ID of a spell if the script was created within spell impact script (or AOE script and few special cases) or -1 if used outside
Most of this is well explained in the functions pages, except creator so lets take a closer look to it:
Creator is the most used way how to determine if a specific effect comes from some ability or item or whatever you want to check. To do that, first we need to know how is creator determined and how to set him (after an effect is creater there is no way to change it AFAIK).
Assume a script in the OnPlayerEquipItem event. If we declare an effect in this script, its creator will be a Module as Module is OBJECT_SELF in this script. It has nothing to do with an object that applied (ApplyEffectToObject()) the effect!!!
{
object oSelf = OBJECT_SELF;//Module
object oPC = GetPCItemLastEquippedBy();
object oItem = GetPCItemLastEquipped();
effect eHaste = EffectHaste();
object oCreator = GetEffectCreator(eHaste);
SendMessageToPC(oPC,"oCreator == oSelf => "+IntToString(oCreator == oSelf));
//The messages printed to PC is: "oCreator == oSelf => 1(TRUE)"
}
To set the haste effect creator other than module, we must declare this effect outside of the current script. So if we want to set its creator to be the item instead, we have two choices how to do that:
1) AssignCommand
2) ExecuteScript
{
effect eHaste = EffectHaste();//creator is item
ApplyEffectToObject(DURATION_TYPE_PERMANENT,eHaste,oPC)
}
void main()
{
object oSelf = OBJECT_SELF;//Module
object oPC = GetPCItemLastEquippedBy();
object oItem = GetPCItemLastEquipped();
effect eHaste = EffectHaste();
AssignCommand(oItem, ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectHaste(), oPC));//most direct way
AssignCommand(oItem, ApplyEffectWithNewCreator(PC));//this is more suitable when you need to apply big link effect
ExecuteScript("new_script", oItem);//in the script new_script would be the same whats in the ApplyEffectWithNewCreator function
AssignCommand(oItem, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eHaste, oPC));//This is wrong way to do, the creator of the eHaste is still Module in this case!!
}
And now, how to use this knowhow
So why the hell would I want to care about this?
Lets say you have an item and you want this item to give a special visual effect after equipping and remove this effect after unequipping. It will work fine till you make second such item with different visual effect, when each item will remove both visual effects after unequipping. To avoid this each effect must have different creator:
(A following example is using tag-based scripting!)
{
object oPC;
object oItem;
effect e;
switch (GetUserDefinedItemEventNumber())
{
case X2_ITEM_EVENT_EQUIP:
// * This code runs when the item is equipped
// * Note that this event fires for PCs only
oPC = GetPCItemLastEquippedBy(); // The player who equipped the item
oItem = GetPCItemLastEquipped(); // The item that was equipped
AssignCommand(oItem, ApplyEffectToObject(DURATION_TYPE_PERMANENT, SupernaturalEffect(EffectVisualEffect(VFX_DUR_AURA_COLD)), oPC));
break;
case X2_ITEM_EVENT_UNEQUIP:
// * This code runs when the item is unequipped
// * Note that this event fires for PCs only
oPC = GetPCItemLastUnequippedBy(); // The player who unequipped the item
oItem = GetPCItemLastUnequipped(); // The item that was unequipped
effect e = GetFirstEffect(oPC);
while(GetIsEffectValid(e))//loop all effects
{
if(GetEffectCreator(e) == oItem)//if effect was created by this item
{
RemoveEffect(oPC,e);//remove it
}
e = GetNextEffect(oPC);
}
break;
}
}
In some cases, eg. adding an attack bonus for taking some feat, its needed to make the creator. Either manually by placing some placeable with specific tag into unaccessable area or automatically via script. For each such effect, a unique creator is needed. See this example:
(A following example is a script called from various events: OnClientEnter, OnPlayerLevelUp, etc.)
{
object oPC = OBJECT_SELF;
//1) remove all previous effects since situation changed and player might lost them due level down
effect e = GetFirstEffect(oPC);
while(GetIsEffectValid(e))//loop all effects
{
if(GetTag(GetEffectCreator(e) == "EC_AB")//if creator's tag is "EC_AB" we know the effect comes from this script
{
RemoveEffect(oPC,e);//remove it
}
e = GetNextEffect(oPC);
}
//2) get the creator
object oCreator = GetObjectByTag("EC_AB");
if(!GetIsObjectValid(oCreator))//creator doesn't exist! we need to create him then
{
location lWhere = GetLocation(oPC);//alternatively we can use GetStartingLocation() or set the Z axis to -50 to make sure player never see it
oCreator = CreateObject(OBJECT_TYPE_PLACEABLE,"plc_invisobj",lWhere,FALSE,"EC_AB");
ApplyEffectToObject(DURATION_TYPE_PERMANENT,ExtraordinaryEffect(EffectVisualEffect(VFX_DUR_CUTSCENE_INVISIBILITY)),oCreator);//hide it
SetPlotFlag(oCreator,TRUE);//protect it from being destroyed
}
//3) check if player fulfils appropriate conditions and calculate the AB bonus
int nBonus;
if(GetHasFeat(1234,oPC))//maybe legendary prowess or something like that
{
nBonus+= 2;
}
if(GetHasFeat(4321,oPC))//maybe paragon prowess or something like that
{
nBonus+= 4;
}
//4) apply the bonus
if(nBonus > 0)
AssignCommand(oCreator, ApplyEffectToObject(DURATION_TYPE_PERMANENT,SupernaturalEffect(EffectAttackIncrease(nBonus)),oPC);
}
Unfortunately, changing a creator in a spell impact script results in losing the spell id. If you need the effect retained its spell id, you have to use other way how to deal with it - and that would be to use the effect ID.
What wasn't said yet is that effect ID doesn't change after applying the effect to the player via ApplyEffectToObject() function.
ApplyEffectToObject(DURATION_TYPE_PERMANENT,eHaste,oPC);//apply effect
effect e = GetFirstEffect(oPC);
while(GetIsEffectValid(e))//loop all effects
{
if(e == eHaste)//effect on character matches with eHaste!
{
RemoveEffect(oPC,e);//remove it
}
e = GetNextEffect(oPC);
}
Even better, you don't need to loop all effects if you have an effect with correct ID:
ApplyEffectToObject(DURATION_TYPE_PERMANENT,eHaste,oPC);//apply effect
RemoveEffect(oPC,eHaste);//remove it
Now, assume a situation where you mustn't change the creator like the previously mentioned spell impact script. We want to make an effect which we can remove if certain conditions happens but it must retain spell id and all other spell informations (as long as effect has spell id, there are also stored values like caster level of the spellcaster etc. and these values are used by certain functions such as EffectDispellMagic(Best/All)).
More specifically: assume a offensive spell that applies a disease on a victim and retain a +1 to the attack bonus as long as victim has this disease. Since the disease can be later removed by remove disease spell or other methods, we need to check repeatedly (with pseudo heartbeat) if victim still has it till she get rid of it and then cancel the bonus.
{
int bStillHasDisease = FALSE;
effect e = GetFirstEffect(oVictim);
while(GetIsEffectValid(e))//loop all effects
{
if(e == eDisease)
{
bStillHasDisease = TRUE;
break;
}
e = GetNextEffect(oVictim);
}
if(bStillHasDisease)//victim still has disease, keep checking
{
DelayCommand(6.0, CheckDiseaseOnVictim(oPC, oVictim, eDisease, eBonus));
}
else
{
RemoveEffect(oPC, eBonus);//if oPC lost the eBonus nothing happens
}
}
void main()//spell impact script
{
object oCaster = OBJECT_SELF;
object oVictim = GetSpellTargetObject();
effect eDisease = SupernaturalEffect(EffectDisease(DISEASE_ZOMBIE_CREEP));//we dont want to dispell it
effect eBonus = EffectAttackIncrease(1);//attack bonus can be dispelled however
if(!GetIsImmune(oVictim, IMMUNITY_TYPE_DISEASE, oCaster))//we need to check if victim isn't immune
{
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eDisease, oVictim);//apply effect
ApplyEffectToObject(DURATION_TYPE_PERMANENT, eBonus, oPC);//apply effect
CheckDiseaseOnVictim(oPC, oVictim, eDisease, eBonus);
}
}
As you can see this is very powerful and allows very elaborated features.
This should be enough as an introduction to the effects. If you have any questions, use the talk page here on wiki. Shadooow.
author: Shadooow