/* ---------------- prc_inc_onhit.nss ---------------- functions for instant spell casting and onhitcasting created April 15, 2007 by motu99 */ /** * new functions for a highly flexible onhitcast system * and for instant spell casting (without having to assign an action) * added by motu99; April 15, 2007 * * most of this has been tested; some is still beta * * In principle these functions should allow us to * i) instantly cast *any* type of spell listed in spells.2da * ii) put any conceivable spell (and as many as we like) on an item * * i) instant spell casting * The new functions CastSpellAtObject() and CastSpellAtLocation() will cast any spell listed in spells.2da * instantaneously, without having to insert the spell into the action queue of the caster * (and thus not knowing when the spell actually will be done, if ever). * * ii) onhit casting * We can now "convert" any type of spell (listed in spells.2da) to an onhitcast spell * we can put as many onhitcast spells on an item as we want * all onhitcast spells on an item will be cast automatically, when the bearer of the item * has scored a hit (item = weapon) or has received a hit (item= armor) */ /** * Making your spells compatible with PRC instant spell casting * using the provided functions CastSpellAtObject() and CastSpellAtLocation() * * What you have to do: * * Insert PRC wrappers into the spell script * * How do you do it? * * Edit your spell script, replacing all calls to the spell "information" functions * - GetSpellCastItem, * - GetSpellTargetObject, * - GetSpellTargetLocation, * - GetMetaMagicFeat, * - GetCasterLevel, * - GetSpellID * - GetLastSpellCastClass, * - etc. * with the respective PRC wrappers * [just add "PRC" to the left of the name of the respective "information" function] * * Nothing else needs to be done * * However, you might want to check, whether you should use PRC functions for other things, * as the Bioware functions don't take the special abilities of the PRC-classes into account. * For instance you might want to use: * - PRCDoResistSpell * - PRCMySavingThrow * - PRCGetHasSpell * - PRCGetSpellLevel * - MyFirstObjectInShape * - MyNextObjectInShape * - PRCGetSaveDC * - PRCGetSaveDC * - PRCDoResistSpell * - PRCGetSpellResistance * - etc. * */ /** * Making your onhitcast spells compatible with PRC onhitcast: * * What you have to do: * i) insert PRC wrappers into the impact spell script * ii) route the impact script through prc_onhitcast * iii) register your onhitcast spell in the (two) 2das * iv) provide a way to place your onhitcast spells on the item * v) provide a way to retrieve the caster level, save dc, etc. from the item * * How do you do it? * * i) insert PRC wrappers into the impact spell script * see the isntructions for instant spell casting * * ii) route the impact script through prc_onhitcast * [do this only for onhitcast spells! Not for "normal" spells!] * * if you want to convert a "normal" spell to an onhitcast spell, * it is strongly advised to make a new spell script * * In order to circumvent the bioware bug, according to * which only the first onhitcast spell on an item is executed, * we must route all onhitcast spells through prc_onhitcast. * * This is done very conveniently by placing the following code * into the body of the main function of your onhitcast impact spell script: * * if(!ContinueOnHitCastSpell()) return; * * Put this at the very beginning of the main() * See "x2_s3_flamingd" for an example * * iii) register your spell in the 2das * [if your spell is operational, you probably have done this before] * * add a line in the two 2da files * - spells.2da * - iprp_onhitspell.2da * look up some onhitcast spells in the 2das to see what must be done * see the instructions under iv) for further explanation * * iv) provide a way to place your onhitcast spells on the item * [if your spell is operational you might have done this before] * * usually the placement of an onhitcast spell on an item is done by a "normal" spell. * For instance the "normal" Flame Weapon spell will put a special onhitcast * item property on the weapon you targeted with the "normal" spell. The item property * has an integer valued subtye, that defines what spell is to be called on a hit. * See "x2_s0_enhweap" for an example how to put the onhitcast spell item property * on an item. * * How does the engine find the spell, that it is supposed to call on a hit, * from the item property? * * The integer valued item property subtype specifies a line in iprp_onhitspell.2da. * For the flame weapon spell it is line # 124. The column "SpellIndex" in * iprp_onhitspell.2da points contains the spellID of the Impact Spell Script. This * is the spell script that will be executed on a hit. For the flame weapon spell it is # 696. * That number specifies a line in spells.2da. This line contains all information about * the spell, in particular the name of the spell script that should be executed to actually * apply the spell's onhit effects. You find the script's name in the column "ImpactScript". * [Note that the flame weapon spell itself occupies a different line # 542 in spells.2da] * The impact script for the "onhit" part of the flame weapon spell, as can be read * off from the column "ImpactScript" on line #696 of spells.2da is "X2_S3_FlamingD" * This is the string we must pass to ExecuteScript() in order to apply the onhitcast spell. * [Note that the script name does not distinguish between lower and higher case letters] * * v) provide a way to retrieve the caster level, save dc, etc. from the item * A "problem" in onhit casting is, that the item from which a spell is cast, must not * necessarily be in the possession of the caster, who put the spell on the item. * It might even be that the caster is not there any more (dead, exited the module) * Therefore we must provide a way to retrieve important information about the * spell (such as caster level, save DC, metamagics, etc.) from the item. This can * be done (in a more or less standard way) via item properties, or (in a non-standard) * way by storing the required information in local ints / objects attached to the item. * If you place the relevant information as item properties on the item, you can retrieve * the information with the standard PRC wrappers PRCGetCasterLevel, PRCGetMetaMagicFeat etc. * in the impact spell script. If you place the relevant information in local ints / objects * stored on the item, you must set up your impact spell script in a "non-standard" way, * so that it retrieves the necessary information via the local variables on the item. * The second route has been used for the flame weapon and darkfire impact spell * scripts (x2_s3_flamingd, x2_s3_darkfire). The first route might be preferrable * when you want to put a "normal" already existing spell on an item and don't want * to modify the impact spell script extensively. */ ////////////////////////////////////////////////// /* Function Prototypes */ ////////////////////////////////////////////////// /** * ExecuteSpellScript: * * instantly executes the spell script sScript via ExecuteScript in the context of oCaster * (meaning that oCaster is considered to be the caster) * does not perform any checks, whether oCaster can actually cast the spell * [You can do the checks in the spell script, for instance by calling X2PreSpellCastCode] * * Remark motu99: * no prespell code, no caster checks: This appears to be the general behavior of most onhitcast spells: * As far as I could tell, the onhitcast spells on weapon and armor generally do not call X2PreSpellCastCode * and - of course - the caster (or rather the item possessor) must not necessarily have to ability to cast spells * (a fighter can use a weapon with "flame weapon" on hit, although it was not *he* who originally * put the spell on the weapon) * * ExecuteSpellScript sets local override ints/objects/locations on oCaster just before execution * and deletes them immediately after execution. * Overrides are generally required, so that the PRC-wrapper functions in the spell scripts can determine * - the target oTarget of the spell (accessed in the spell script via PRCGetSpellTargetObject), * - the location lLocation for an AoE spell (accessed in the spell script via PRCGetSpellTargetLocation) * - the metamagic feat of the spell nMetamagic (accessed in the spell script via PRCGetMetaMagicFeat) * - the casterlevel nCasterLevel (accessed in the spell script via PRCGetCasterLevel) * - the spell cast item oItem (accessed in the spell script via PRCGetSpellCastItem) * - etc. * * If default values for the parameters oTarget, nMetaMagic, nCasterLevel, oItem etc. are passed to ExecuteSpellScript, * it does not set any overrides for the PRC-wrapper functions. In this case you have to rely on the "standard" logic in * the PRC wrapper functions to properly determine the correct values. * The standard logic generally works fine for nCasterLevel, but might not work as expected for oTarget, nMetaMagic * and oItem - depending from where you call ExecuteSpellScript. * If you call ExecuteSpellScript from *within a spell script* (so that the PRC - or Bioware's - initialization * codes had a chance to set up things nicely) then the spell "information" functions PRCGetSpellTargetObject, * PRCGetMetaMagicFeat etc. will most likely return sensible values. (even the Bioware functions might return useful info) * However, if you call ExecuteSpellScript *outside of a spell script*, nobody will have done any setup for you. * In that case you are strongly advised to setup things manually, e.g. determine oTarget, nMetaMagic, nCasterLevel, * oItem, etc. on your own and pass them to ExecuteSpellScript without relying on the "standard" logic to do the guessing for you. * * In principle ExecuteSpellScript(), in combination with the PRC-wrappers, is the only functions you really need for instantaneous spell casting. * Most other functions, such as CastSpellAtObject, CastSpellAtLocation are provided as a convenience. * They mimic the behavior of Bioware's ActionCastSpell* commands. They all eventually call ExecuteSpellScript */ void ExecuteSpellScript(string sScript, location lTargetLocation, object oTarget = OBJECT_INVALID, int nSpellID = 0, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); /** * CastSpellAtObject: * * similar to Bioware's ActionCastSpellAtObject * * Will instantaneously cast the spell with index iSpellNr at oTarget * The spell is not inserted into the action queue of oCaster, no checks whether oCaster can actually cast the spell * oTarget is the spell's target, oItem (if required) the item from which the spell is cast, oCaster (or OBJECT_SELF) is considered to be the caster * tested for flame weapon and darkfire impact spell scripts, but should eventually work for all spells (see below) * * in order to work, the spell scripts associated with iSpellNr (via spells.2da) must use the PRC-wrapper functions!!! * Besides the established wrappers (PRCGetSpellTargetObject, PRCGetCasterLevel, PRCGetMetaMagicFeat, PRCGetSpellId etc.) * we need a new PRC-wrapper function to replace SpellCastItem(). Not surprising the wrapper is named PRCGetSpellCastItem() * It would be a good idea for any spell coder, who wants to remain compatible with the PRC, to check his spell scripts, * whether all calls to Bioware's spell information functions have been replaced by the respective PRC wrapper functions. * [As far as I could tell, this is not done consistently] * * If default values for oTarget, nMetaMagic, nCasterLevel and oItem are supplied, no override variables are set * In this case the PRC-wrapper functions (or Bioware's original functions) must determine the SpellTargetObject, * the SpellCastItem etc. through their "standard" logic. This might or might not work. * For more information see the description in ExecuteSpellScript() */ void CastSpellAtObject(int nSpellID, object oTarget = OBJECT_INVALID, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_INVALID, object oCaster = OBJECT_SELF); // instantaneously casts an area of effect spell at location lTargetLocation // works similar to ActionCastSpellAtLocation, but casts the spell instantly // See the description of CastSpellAtObject, how instant spell casting work void CastSpellAtLocation(int nSpellID, location lTargetLocation, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_INVALID, object oCaster = OBJECT_SELF); // applies the onhitcast spell with subtype iSubType (situated on oItem ) to oTarget // will look up the spell script that must be executed through "iprp_onhitspell.2da" and "spells.2da" // if default values for oTarget, oItem are supplied, no override variables are set // for more information see the description in ExecuteSpellScript() void ApplyOnHitCastSpellSubType(int iSubType, object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); // applies the onhitcast spell darkdire (situated on oItem) to oTarget // will look up the spell script that must be executed through "iprp_onhitspell.2da" and "spells.2da" // if default values for oTarget, oItem are supplied, no override variables are set // for more information see the description in ExecuteSpellScript() void ApplyOnHitDarkfire(object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); // applies the onhitcast spell flame weapon (situated on oItem) to oTarget // will look up the spell script that must be executed through "iprp_onhitspell.2da" and "spells.2da" // if default values for oTarget, oItem are supplied, no override variables are set // for more information see the description in ExecuteSpellScript() void ApplyOnHitFlameWeapon(object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); // applies the onhitcast spell unique power (situated on oItem) to oTarget // actually this will call the script "prc_onhitcast" (hardcoded), e.g. it will *not* look up the 2das (as the other functions do) // if default values for oTarget, oItem are supplied, no override variables are set // for more information see the description in ExecuteSpellScript() void ApplyOnHitUniquePower(object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); // applies all on hit cast spells on oItem to oTarget // will look up the spell scripts that must be executed in "iprp_onhitspell.2da" and "spells.2da" // Uses a "safe" to cycle through the item properties, without interfering with any other loops // over item properties (that for instance could be initiated in the spell scripts) // Always use this function to cycle through and execute several onhitcast spells on an item!! // If default values for oTarget, oItem are supplied, no override variables are set // for more information see the description in ExecuteSpellScript() void ApplyAllOnHitCastSpellsOnItem(object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); // applies all on hit cast spells on oItem, excluding any spell-subtype given in iExcludeSubType, to oTarget // will look up the spell script that must be executed in "iprp_onhitspell.2da" and "spells.2da" // Uses a "safe" to cycle through the item properties, without interfering with any other loops // over item properties (that for instance could be initiated in the spell scripts) // Always use this function to cycle through and execute several onhitcast spells on an item!! // if default values for oTarget, oItem are supplied, no override variables are set // for more information see the description in ExecuteSpellScript() void ApplyAllOnHitCastSpellsOnItemExcludingSubType(int iExcludeSubType, object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); // returns True, if oItem has at least one onhitcast spell on it (can be any subtype) int GetHasOnHitCastSpell(object oItem); // returns True, if the oItem has an onhitcast spell with the given subtype int GetHasOnHitCastSpellSubType(int iSubType, object oItem); // returns True, if we have the onhit flame weapon spell on oItem int GetHasOnHitFlameWeapon(object oItem); // returns True, if we have the onhit darkfire spell on oItem int GetHasOnHitDarkfire(object oItem); // returns True, if we have the onhit unique power spell on oItem int GetHasOnHitUniquePower(object oItem); // returns true, when prc_onhitcast is running, e.g. the spell script is called from there int GetIsPrcOnhitcastRunning(object oSpellOrigin = OBJECT_SELF); // to be used ONLY in "pure" on-hit cast spell scripts, in order to route all // onhitcast spells through the unique power script (prc_onhitcast) // This function should be placed at the beginning of any pure onhitcast spell script // If the onhitcast spell script contains a call to X2PreSpellCastCode(), replace that call with a call to ContinueOnHitCastSpell() // This code needs to be in the spell script, in order to neutralize a Biobug // (Bioware only executes the first onhitcast spell found on an item) // Checks the local int "prc_ohc"; if it does not find the int, it assumes the spell script was called // directly by the aurora engine. In that case it will call prc_onhitcast and return FALSE, // FALSE meaning that the spell script should be aborted // if the function returns TRUE; the spell script was called from prc_onhitcast. In this case prc_onhitcast // guarantees the execution of all onhitcast spells on an item, so the spell script should continue to run /** * Remark motu99: * The behavior of ContinueOnHitCastSpell is very similar to that of X2PreSpellCastCode() * In fact, it should replace any instances of X2PreSpellCastCode() for "pure" onhitcast spells. * * For spells that are not exclusively onhitcast spells (e.g. normal spells that some mages * can put on a weapon), we should not use ContinueOnHitCastSpell, but rather call X2PreSpellCastCode. * In this case X2PreSpellCastCode (in x2_inc_spellhook) will have to check, whether the spell script was * called via an onhitcast event connected to a successful physical attack and route to prc_onhitcast in such a case * (Such a general check from X2PreSpellCastCode is not 100% reliable, because of Biowares buggy implementation. * However, the function GetIsOnHitCastSpell() provided here will do a very good job at guessing.) * * For pure onhitcast spells it is safer and much more efficient to use ContinueOnHitCastSpell instead * of X2PreSpellCastCode. */ int ContinueOnHitCastSpell(object oSpellOrigin = OBJECT_SELF); // Checks, whether the spell just running is an onhitcast spell, cast from an item // Function is to be used in X2PreSpellCastCode() for any onhitcast specific modifications // (its main use is to fix the Bioware bug, that runs only the first onhitcast spell on a weapon or armor) // The check is not 100% reliable, because Bioware does not delete the spellcastitem after a spell code has finished. // So if GetSpellCastItem returns a valid item, we still don't know if we are running an onhitcast event, // even if the item is a weapon or armor. The item could be an old item used in a previous spell script. // (for instance, sequencer's robes count as armor) // In order to find out, whether the spell was actually cast from a weapon/armor in a physical attack action // we check whether the current action is ACTION_ATTACKOBJECT and whether the attempted attack // target is equal to the spell's target int GetIsOnHitCastSpell(object oSpellTarget = OBJECT_INVALID, object oSpellCastItem = OBJECT_SELF, object oSpellOrigin = OBJECT_SELF); // this will force the instantaneous execution of any onhitcast spell script, even if it is set up to be // routed through prc_onhitcast (for more info on forced execution look at the code of ContinueOnHitCastSpell) // For more info on functionality, see the description/code of ExecuteSpellScript() void ForceExecuteSpellScript(string sScript, location lTargetLocation, object oTarget = OBJECT_INVALID, int nSpellID = 0, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); // this will force the instantaneous execution of any onhitcast spell script with iSpellNr, as given in spells.2da // The spell script will be executed, even if it has been set up to be routed through prc_onhitcast // (for more info on forced execution look at the code of ContinueOnHitCastSpell) // for more info on functionality, see the description/code of CastSpellAtObject void ForceCastSpellAtObject(int nSpellID, object oTarget = OBJECT_INVALID, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_INVALID, object oCaster = OBJECT_SELF); void ForceCastSpellAtLocation(int nSpellID, location lTargetLocation, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_INVALID, object oCaster = OBJECT_SELF); // forces the instantaneous application of the onhitcast spell with subtype iSubType (situated on oItem) to oTarget // will force apply the spell scripts even if they have internally been set up to be routed through prc_onhitcast // (for more info on forced execution look at the code of ContinueOnHitCastSpell) // for more info on functionality, see the description/code of ApplyOnHitCastSpellSubType void ForceApplyOnHitCastSpellSubType(int iSubType, object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); // instantaneously applies all on hit cast spells on oItem to oTarget // will force apply the spell scripts even if they have internally been set up to be routed through prc_onhitcast // (for more info on forced execution look at the code of ContinueOnHitCastSpell) // for more info on functionality, see the description/code of ApplyAllOnHitCastSpellsOnItem // This is the safe way to loop through the item properties, without interfering with any other loops over item properties in the spell scripts void ForceApplyAllOnHitCastSpellsOnItem(object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); // most likely we will never need the following function: // instantaneously applies all on hit cast spells on oItem, excluding any spell-subtype given in iExcludeSubType, to oTarget // will force apply the spell scripts even if they have internally been set up to be routed through prc_onhitcast // (for more info on forced execution look at the code of ContinueOnHitCastSpell) // for more info on functionality, see the description/code of ApplyAllOnHitCastSpellsOnItemExcludingSubType // Uses a "safe" way to loop through the item properties, without interfering with any other loops over item properties in the spell scripts void ForceApplyAllOnHitCastSpellsOnItemExcludingSubType(int iExcludeSubType, object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF); ////////////////////////////////////////////////// /* Includes */ ////////////////////////////////////////////////// #include "inc_abil_damage" ////////////////////////////////////////////////// /* Function Definitions */ ////////////////////////////////////////////////// /** * ExecuteSpellScript * executes the spell script sScript via ExecuteScript in the context of oCaster * sets local override ints/objects on oCaster before execution (and deletes them immediately after execution) * oTarget, nMetaMagic, nCasterLevel, oItem, nSpellID, nCasterClass, lTargetLocation * can be accessed in the spell script via PRC-wrappers * * known caveats: * Using calls to PRCGetMetaMagicFeat() in onhitcast spells can be dangerous, because PRCGetMetaMagicFeat() * will cycle through the item properties in order to find out whether there are any properties * of the type ITEM_PROPERTY_CAST_SPELL_METAMAGIC. This is problematic, because prc_onhitcast must also * cycle through the item properties, in order to find out what onhitcast spells are on the item. * If we are not careful, we might find ourselves with two nested loops over item properties. But the current implementation * of GetFirstItemProperty and GetNextItemProperty does not allow nested loops. Nested loops generally result * in unpredictable behavior (such as infinitely returning the same item property on GetNextItemProperty) * * Remark motu99: * The above described problem ALWAYS occurs when there are nested loops over item properties or effects. * Therefore generally it is a bad idea to call anything complicated (e.g. a spell script!) within a loop over item properties * or effects, in particular if you don't know every detail of the so called function. If the called function contains just * one single call to GetFirst* or GetNext* (this could happen deep in the function's code - possibly in a utility function * several calling levels deeper), this will mess up your next call to GetNext*. * I provided a "safe" way to cycle through all onhitcast spells on an item: ApplyAllOnHitCastSpellsOnItem * * known limitations: * some spell scripts need the cast class (e.g. did we cast the spell as a cleric, a wizard, a druid etc.) * in instantaneous casting we do not select the spell from a spellbook (which is always tied to a certain class), so that the aurora * engine cannot set a sensible value for GetLastSpellCastClass(). Thus, if the spell script needs it, we must determine the cast * class ourselves, pass it to ExecuteSpellScript and make sure we call the PRC wrapper PRCGetLastSpellCastClass in the spell script * * In the current implementation the spell's save DC is not calculated correctly. It generally returns the minimum DC. * The reason is, that PRCGetSaveDC (in prc_add_spell_dc.nss) has not yet been updated to handle instant spell casting. * In particular, PRCGetSaveDC calls Biowares GetSpellDC(), which returns the minimum DC when called *outside* a * spell script. As instant spellcasting is usually called from outside a spell script, we get only get the minimum DC. * @TODO: In order to determine the correct DC, we need to know the casting class [this will anable us to select the * correct ability adjustments - for instance INT for Wizards, CHA for sorcerers, etc.]. So whenever there is a * cast class override, we should not call Bioware's GetSpellDC, but rather calculate the DC directly. * * some spell scripts need the spell ID. It is automatically set by CastSpellAtObject or CastSpellAtLocation. We can set it in * ExecuteSpellScript as well. * * some spell scripts make calls to GetLastSpell(). We might need a PRC wrapper to catch those. * In any case, PRCGetSpellId will give us the correct spell ID. GetLastSpell seems to be used in OnSpellCastAt events * From the NWNLexicon: GetLastSpell is for use in OnSpellCastAt script, it gets the ID (SPELL_*) of the spell that was cast. * It will not return the true spell value, but whatever value is put into the EventSpellCastAt() - therefore, sub-dial spells like * Polymorph will mearly return SPELL_POLYMORPH not the sub spell number * * some spell scripts make calls to GetLastSpellCaster(). In a spell script OBJECT_SELF should be the caster. Its not clear * what a call to GetLastSpellCaster() will return. Might have to look into this * */ void ExecuteSpellScript(string sScript, location lTargetLocation, object oTarget = OBJECT_INVALID, int nSpellID = 0, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { if(DEBUG) DoDebug("ExecuteSpellScript: executing script "+sScript); // create an invalid location by not assigning anything to it (hope this works) location lInvalid; // tell the impact spell script where the target area is if (lTargetLocation != lInvalid) { SetLocalInt(oCaster, PRC_SPELL_TARGET_LOCATION_OVERRIDE, TRUE); SetLocalLocation(oCaster, PRC_SPELL_TARGET_LOCATION_OVERRIDE, lTargetLocation); } // tell the impact spell script who the target is if (oTarget != OBJECT_INVALID) SetLocalObject(oCaster, PRC_SPELL_TARGET_OBJECT_OVERRIDE, oTarget); // tell the impact spell script what the spell cast item is if (oItem != OBJECT_SELF) { //DoDebug("ExecuteSpellScript: setting override spell cast item = "+GetName(oItem)); if (oItem == OBJECT_INVALID) SetLocalObject(oCaster, PRC_SPELLCASTITEM_OVERRIDE, oCaster); else SetLocalObject(oCaster, PRC_SPELLCASTITEM_OVERRIDE, oItem); } // override the caster level, but only if necessary if (nCasterLevel) SetLocalInt(oCaster, PRC_CASTERLEVEL_OVERRIDE, nCasterLevel); // tell the impact spell script the metamagic we want to use if (nMetaMagic != METAMAGIC_ANY) SetLocalInt(oCaster, PRC_METAMAGIC_OVERRIDE, nMetaMagic); // tell the impact spell script the spellID we want to use // check for zero is problematic becauseSPELL_ACID_FOG == 0 if (nSpellID) SetLocalInt(oCaster, PRC_SPELLID_OVERRIDE, nSpellID); // tell the impact spell script the caster class we want to use // check for zero is problematic, because CLASS_TYPE_BARBARIAN == 0 // But Barbarians cannot cast spells (UMD? SPA?), so this should work if (nCasterClass) SetLocalInt(oCaster, PRC_CASTERCLASS_OVERRIDE, nCasterClass); if (nSaveDC) SetLocalInt(oCaster, PRC_SAVEDC_OVERRIDE, nSaveDC); // execute the impact spell script in the context of oCaster ExecuteScript(sScript, oCaster); /** * motu99: If we were paranoid, we could delete all local ints and objects, regardless if we set them beforehand, * as a "countermeasure" against any future scripter, who might disregard the given advise, set the overrides manually * and then forget to delete them. * However, as long as scripters follow the advise, and use ExecuteSpellScript and the other functions provided here, * never manually setting the overrides, we can be sure, that these variables are not misused. * In the unlikely case that somebody did not play by the rules, and managed to break "normal" spell casting by improperly * using the overrides, and we cannot locate where and what he did, we might have to resort to the last measure, deleting * all overrides on every call to ExecuteSpellScript() and ExecuteAoESpellScript() * If we delete the overrides indiscriminately, we MUST ALWAYS call ExecuteSpellScript with NON-DEFAULT values. * Otherwise we are in trouble when we do multiple calls to ExecuteScript from a loop: We might accidentally delete * any overrides that we set on some previous call to ExecuteSpellScript. At that point we are at the mercy of * Bioware's functions to provide the correct values, which does not always work (to put it mildly) * Note that some measure of safety is provided through the fact, that we set the overrides on oCaster, and not on the module */ // cleanup (we only delete those locals that we set before) if (lTargetLocation != lInvalid) { DeleteLocalInt(oCaster, PRC_SPELL_TARGET_LOCATION_OVERRIDE); // DeleteLocalLocation(oCaster, PRC_SPELL_TARGET_LOCATION_OVERRIDE); } if (oItem != OBJECT_SELF) DeleteLocalObject(oCaster, PRC_SPELLCASTITEM_OVERRIDE); if (oTarget != OBJECT_INVALID) DeleteLocalObject(oCaster, PRC_SPELL_TARGET_OBJECT_OVERRIDE); if (nMetaMagic != METAMAGIC_ANY) DeleteLocalInt(oCaster, PRC_METAMAGIC_OVERRIDE); if (nCasterLevel) DeleteLocalInt(oCaster, PRC_CASTERLEVEL_OVERRIDE); if (nSpellID) DeleteLocalInt(oCaster, PRC_SPELLID_OVERRIDE); if (nCasterClass) DeleteLocalInt(oCaster, PRC_CASTERCLASS_OVERRIDE); if (nSaveDC) DeleteLocalInt(oCaster, PRC_SAVEDC_OVERRIDE); if (DEBUG) DoDebug("ExecuteSpellScript: done executing script "+sScript); } // ExecuteSpellScript will set PRC_SPELLID_OVERRIDE, so that we know what spell we are casting // However, SPELL_ACID_FOG has a SpellID of zero, so we might have problems with this particular spell void CastSpellAtObject(int nSpellID, object oTarget = OBJECT_INVALID, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_INVALID, object oCaster = OBJECT_SELF) { // get the name of the impact spell script (for ExecuteScript) // nSpellID points to a row in spells.2da string sScript = Get2DACache("spells", "ImpactScript", nSpellID); if(sScript == "" || sScript == "****") return; // create an invalid location location lInvalid; // execute the spell script ExecuteSpellScript(sScript, lInvalid, oTarget, nSpellID, nMetaMagic, nCasterLevel, nCasterClass, nSaveDC, oItem, oCaster); } // works similar to ActionCastSpellAtLocation, only casts spell instantly (without saving throw etc. // See description of CastSpellAtObject, how instant spells work void CastSpellAtLocation(int nSpellID, location lTargetLocation, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_INVALID, object oCaster = OBJECT_SELF) { // get the name of the impact spell script (for ExecuteScript) string sScript = Get2DACache("spells", "ImpactScript", nSpellID); if(sScript == "" || sScript == "****") return; // execute the spell script ExecuteSpellScript(sScript, lTargetLocation, OBJECT_INVALID, nSpellID, nMetaMagic, nCasterLevel, nCasterClass, nSaveDC, oItem, oCaster); } void ApplyOnHitCastSpellSubType(int iSubType, object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { // get the spellID of the onhitspell int iSpellID = StringToInt( Get2DACache("iprp_onhitspell", "SpellIndex", iSubType) ); // now execute the impact spell script CastSpellAtObject(iSpellID, oTarget, METAMAGIC_ANY, 0, 0, 0, oItem, oCaster); } void ApplyOnHitDarkfire(object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { ApplyOnHitCastSpellSubType(IP_CONST_ONHIT_CASTSPELL_ONHIT_DARKFIRE, oTarget, oItem, oCaster); } void ApplyOnHitFlameWeapon(object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { ApplyOnHitCastSpellSubType(IP_CONST_ONHIT_CASTSPELL_ONHIT_FIREDAMAGE, oTarget, oItem, oCaster); } void ApplyOnHitUniquePower(object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { location lInvalid; // ApplyOnHitCastSpellSubType(IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, oTarget, METAMAGIC_ANY, 0, oItem, oCaster); // unique power hardwired to "prc_onhitcast". If we want to go through the 2das, we should revert to the commented out line of code above ExecuteSpellScript("prc_onhitcast", lInvalid, oTarget, 0, METAMAGIC_ANY, 0, 0, 0, oItem, oCaster); } void ApplyAllOnHitCastSpellsOnItem(object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { int iSubType; int iNr = 0; if(!array_exists(oCaster, "ohspl")) array_create(oCaster, "ohspl"); // remember the item that was passed to the function (in case it is invalid we simply pass it through to the function that does the spells) object oItemPassed = oItem; // we need an item in order to cycle over item properties // if OBJECT_INVALID was given, we must use the "standard" logic in PRCGetSpellCastItem to determine the item if (oItem == OBJECT_SELF) oItem = PRCGetSpellCastItem(oCaster); itemproperty ip = GetFirstItemProperty(oItem); while(GetIsItemPropertyValid(ip)) { // DoDebug("ApplyAllOnHitCastSpellsExcludingSubType: found " + DebugIProp2Str(ip)); if (GetItemPropertyType(ip) ==ITEM_PROPERTY_ONHITCASTSPELL) { // retrieve the spell ID iSubType = GetItemPropertySubType(ip); // we found a new onhit spell, so increment iNr iNr++; // store the spell ID in an array and execute the spell later, this is safer than trying to execute the spell script directly // lets hope that nobody else uses our name "ohspl" for the array array_set_int(oCaster, "ohspl", iNr, iSubType); } ip = GetNextItemProperty(oItem); } // now execute the spell scripts (note that the local array will not be deleted) while (iNr) { iSubType = array_get_int(oCaster, "ohspl", iNr); //DoDebug("ApplyAllOnHitCastSpellsExcludingSubType: executing onhitcastspell subtype # " + IntToString(iSubType)); ApplyOnHitCastSpellSubType(iSubType, oTarget, oItemPassed, oCaster); iNr--; } array_delete(oCaster, "ohspl"); } void ApplyAllOnHitCastSpellsOnItemExcludingSubType(int iExcludeSubType, object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { int iSubType; int iNr = 0; if(!array_exists(oCaster, "ohspl")) array_create(oCaster, "ohspl"); // remember the item that was passed to the function (in case it is invalid we pass it through to the spell cast functions) object oItemPassed = oItem; if (oItem == OBJECT_SELF) { oItem = PRCGetSpellCastItem(oCaster); //DoDebug("ApplyAllOnHitCastSpellsOnItemExcludingSubType: item = OBJECT_SELF, determining item by standard logic, item = " +GetName(oItem)); } itemproperty ip = GetFirstItemProperty(oItem); while(GetIsItemPropertyValid(ip)) { // DoDebug("ApplyAllOnHitCastSpellsExcludingSubType: found " + DebugIProp2Str(ip)); if (GetItemPropertyType(ip) ==ITEM_PROPERTY_ONHITCASTSPELL) { iSubType = GetItemPropertySubType(ip); if(iSubType == iExcludeSubType) // if(iSubType ==IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER) // == 125 { ip = GetNextItemProperty(oItem); continue; //skip over OnHit:CastSpell:UniquePower otherwise it would TMI. } // we found a new onhit spell, so increment iNr iNr++; // store the spell ID in an array and execute the spell later, this is safer than trying to execute the spell script directly // lets hope that nobody else uses our name "ohspl" for the array array_set_int(oCaster, "ohspl", iNr, iSubType); } ip = GetNextItemProperty(oItem); } // now execute the spell scripts (note that the local array will not be deleted) while (iNr) { iSubType = array_get_int(oCaster, "ohspl", iNr); //DoDebug("ApplyAllOnHitCastSpellsExcludingSubType: executing onhitcastspell subtype # " + IntToString(iSubType)); ApplyOnHitCastSpellSubType(iSubType, oTarget, oItemPassed, oCaster); iNr--; } array_delete(oCaster, "ohspl"); } // returns true, if oItem has at least one onhitcast spell (of any subtype) int GetHasOnHitCastSpell(object oItem) { itemproperty ip = GetFirstItemProperty(oItem); while(GetIsItemPropertyValid(ip)) { if (GetItemPropertyType(ip) ==ITEM_PROPERTY_ONHITCASTSPELL) { return TRUE; } ip = GetNextItemProperty(oItem); } return FALSE; } // returns true, if oItem has an onhitcast spell with the given subtype int GetHasOnHitCastSpellSubType(int iSubType, object oItem) { itemproperty ip = GetFirstItemProperty(oItem); while(GetIsItemPropertyValid(ip)) { if (GetItemPropertyType(ip) ==ITEM_PROPERTY_ONHITCASTSPELL && GetItemPropertySubType(ip) == iSubType) { return TRUE; } ip = GetNextItemProperty(oItem); } return FALSE; } // returns True, if we have the onhit flame weapon spell on the item int GetHasOnHitFlameWeapon(object oItem) { return GetHasOnHitCastSpellSubType(IP_CONST_ONHIT_CASTSPELL_ONHIT_FIREDAMAGE, oItem); } // returns True, if we have the onhit darkfire spell on the item int GetHasOnHitDarkfire(object oItem) { return GetHasOnHitCastSpellSubType(IP_CONST_ONHIT_CASTSPELL_ONHIT_DARKFIRE, oItem); } // returns True, if we have the onhit unique power spell on the item int GetHasOnHitUniquePower(object oItem) { return GetHasOnHitCastSpellSubType(IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, oItem); } int GetIsPrcOnhitcastRunning(object oSpellOrigin = OBJECT_SELF) { return GetLocalInt(oSpellOrigin, "prc_ohc"); } // to be used only in on-hit cast spell scripts, in order to route the spell through the unique power script (prc_onhitcast) int ContinueOnHitCastSpell(object oSpellOrigin = OBJECT_SELF) { // if the local int "frc_ohc" is on, then we never route the spell through prc_onhitcast // rather we continue the execution of the onhitcast script; so return True in this case if (GetLocalInt(oSpellOrigin, "frc_ohc")) { // signal to caller that it can continue execution (no rerouting to prc_onhitcast) return TRUE; } // now check whether we were called from prc_onhitcast int bCalledByPRC = GetIsPrcOnhitcastRunning(oSpellOrigin); // if not, call prc_onhitcast if (!bCalledByPRC) { if (DEBUG) DoDebug("onhitcast spell script not called through prc_onhitcast - now routing to prc_onhitcast"); ExecuteScript("prc_onhitcast", oSpellOrigin); } // signal to calling function, whether it should terminate execution, because we rerouted the call (e.g. bCalledByPRC=FALSE) // or whether it should continue execution, because we were called from prc_onhitcast (e.g. bCalledByPRC=TRUE) return bCalledByPRC; } // Checks, whether the spell just running is an onhitcast spell, cast from an item (weapon or armor) during an attack action // Due to Biowares buggy implementation the check is not 100% reliable int GetIsOnHitCastSpell(object oSpellTarget = OBJECT_INVALID, object oSpellCastItem = OBJECT_SELF, object oSpellOrigin = OBJECT_SELF) { // determine spell cast item (if not already given) if (oSpellCastItem == OBJECT_SELF) oSpellCastItem = PRCGetSpellCastItem(oSpellOrigin); // spell cast item must be valid (but it could still be an item from a previous item spell) if (!GetIsObjectValid(oSpellCastItem)) { if (DEBUG) DoDebug("GetIsOnHitCastSpell: no valid spell cast item, returning FALSE"); return FALSE; } // onhitcasting affects weapons and armor; find out which int iBaseType = GetBaseItemType(oSpellCastItem); object oAttacker; object oDefender; int iWeaponType; // is the spellcast item an armor? // Then it could be an onhitcast event from the armor of the defender // the aurora engine executes the onhitcast spell in the context of the bearer of the armor, e.g. the defender if (iBaseType == BASE_ITEM_ARMOR) { // find the target, if not already given if (oSpellTarget == OBJECT_INVALID) oSpellTarget = PRCGetSpellTargetObject(oSpellOrigin); // the spell target of the armor onhitspell is the melee attacker (e.g. oSpellTarget = melee attacker). // The aurora engine exeutes the onhit spell in the context of the melee defender (e.g. oSpellOrigin = melee defender) oAttacker = oSpellTarget; oDefender = oSpellOrigin; if (DEBUG) DoDebug("GetIsOnHitCastSpell: item "+GetName(oSpellCastItem)+" is armor; attacker = "+GetName(oAttacker)+", defender = "+GetName(oDefender)); } // is the spell type item a weapon? else if (iWeaponType == StringToInt(Get2DACache("baseitems", "WeaponType", iBaseType))) { // determine the target, if not already given if (oSpellTarget == OBJECT_INVALID) oSpellTarget = PRCGetSpellTargetObject(oSpellOrigin); oAttacker = oSpellOrigin; oDefender = oSpellTarget; if (DEBUG) DoDebug("GetIsOnHitCastSpell: item "+GetName(oSpellCastItem)+" is weapon [#"+IntToString(iWeaponType)+"]; attacker = "+GetName(oAttacker)+", defender = "+GetName(oDefender)); } else { if (DEBUG) DoDebug("GetIsOnHitCastSpell: item "+GetName(oSpellCastItem)+" is neither weapon nor armor; returning FALSE"); return FALSE; } // the spell origin must possess the item that cast the spell (at least for the aurora engine, in prc_inc_combat that may differ) if (GetItemPossessor(oSpellCastItem) != oSpellOrigin) { if (DEBUG) DoDebug("GetIsOnHitCastSpell: the spell cast item is not in the possession of the creature in which context the spell is executed"); return FALSE; } // the attacker must be physically attacking (only then onhitcasting makes sense) // however, Bioware seems to set the action to ACTION_INVALID during the spell script int iAction = GetCurrentAction(oAttacker); if (DEBUG) DoDebug("GetIsOnHitCastSpell: current action of attacker = "+IntToString(GetCurrentAction(oAttacker))); if (iAction != ACTION_INVALID && iAction != ACTION_ATTACKOBJECT) { if (DEBUG) DoDebug("GetIsOnHitCastSpell: current action of attacker = "+IntToString(GetCurrentAction(oAttacker))+" is not compatible with onhitcasting, return FALSE"); return FALSE; } // the attacker should actually be (melee) attacking the defender if (GetAttackTarget(oAttacker) != oDefender) { if (DEBUG) DoDebug("GetIsOnHitCastSpell: melee attacker of onhitcast spell is not attacking the melee defender, return FALSE"); return FALSE; } // if we made it here, it is an onhitcast spell return TRUE; } // this will force the execution of any onhitcast spell script, even if it is set up to be // routed through prc_onhitcast (see the description for the function ContinueOnHitCastSpell) void ForceExecuteSpellScript(string sScript, location lTargetLocation, object oTarget = OBJECT_INVALID, int nSpellID = 0, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { // set the local int, that tells the spell script that we want to force its execution SetLocalInt(oCaster, "frc_ohc", TRUE); // now call the spell script ExecuteSpellScript(sScript, lTargetLocation, oTarget, nSpellID, nMetaMagic, nCasterLevel, nCasterClass, nSaveDC, oItem, oCaster); // delete the local int for forced execution DeleteLocalInt(oCaster, "frc_ohc"); } // this will force the instant execution of any onhitcast spell script with nSpellID, as given in spells.2da // The spell script will be executed, even if it has been set up to be routed through prc_onhitcast // (for more info, see the description of the function ContinueOnHitCastSpell) void ForceCastSpellAtObject(int nSpellID, object oTarget = OBJECT_INVALID, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_INVALID, object oCaster = OBJECT_SELF) { // get the name of the impact spell script (for ExecuteSpellScript) string sScript = Get2DACache("spells", "ImpactScript", nSpellID); if(sScript == "" || sScript == "****") return; // create an invalid location location lInvalid; // now force-execute the spell script ForceExecuteSpellScript(sScript, lInvalid, oTarget, nSpellID, nMetaMagic, nCasterLevel, nCasterClass, nSaveDC, oItem, oCaster); } // this will force the instant execution of any AoE onhitcast spell script with nSpellID, as given in spells.2da // The spell script will be executed, even if it has been set up to be routed through prc_onhitcast // (for more info, see the description of the function ContinueOnHitCastSpell) void ForceCastSpellAtLocation(int nSpellID, location lTargetLocation, int nMetaMagic = METAMAGIC_ANY, int nCasterLevel = 0, int nCasterClass = 0, int nSaveDC = 0, object oItem = OBJECT_INVALID, object oCaster = OBJECT_SELF) { // get the name of the impact spell script (for ExecuteSpellScript) string sScript = Get2DACache("spells", "ImpactScript", nSpellID); if(sScript == "" || sScript == "****") return; // now force-execute the spell script ForceExecuteSpellScript(sScript, lTargetLocation, OBJECT_INVALID, nSpellID, nMetaMagic, nCasterLevel, nCasterClass, nSaveDC, oItem, oCaster); } // forces the application of the onhitcast spell (situated on oItem) with subtype iSubType to oTarget, in the context of oCaster void ForceApplyOnHitCastSpellSubType(int iSubType, object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { // get the spellID of the onhitspell int iSpellID = StringToInt( Get2DACache("iprp_onhitspell", "SpellIndex", iSubType) ); // now force-execute the impact spell script ForceCastSpellAtObject(iSpellID, oTarget, METAMAGIC_ANY, 0, 0, 0, oItem, oCaster); } // applies all on hit cast spells on oItem to oTarget in the context of oCaster // will force apply the spell scripts even if they have internally been set up to be routed through prc_onhitcast // (for more info, see the description of the function ContinueOnHitCastSpell) // This is the safe way to do this, without interfering with any other loops over item properties in the spell scripts that are called void ForceApplyAllOnHitCastSpellsOnItem(object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { // set the local int, that tells the spell scripts that we want to force their execution SetLocalInt(oCaster, "frc_ohc", TRUE); // now apply all onhitcast spells on the item ApplyAllOnHitCastSpellsOnItem(oTarget, oItem, oCaster); // delete the local int for forced execution DeleteLocalInt(oCaster, "frc_ohc"); } // applies all on hit cast spells on oItem, excluding any spell-subtype given in iExcludeSubType, to oTarget in the context of oCaster // will force apply the spell scripts even if they have internally been set up to be routed through prc_onhitcast // (for more info, see the description of the function ContinueOnHitCastSpell) // This is the safe way to do this, without interfering with any other loops over item properties in the spell scripts that are called void ForceApplyAllOnHitCastSpellsOnItemExcludingSubType(int iExcludeSubType, object oTarget = OBJECT_INVALID, object oItem = OBJECT_SELF, object oCaster = OBJECT_SELF) { // set the local int, that tells the spell scripts that we want to force their execution SetLocalInt(oCaster, "frc_ohc", TRUE); // now apply all onhitcast spells on the item ApplyAllOnHitCastSpellsOnItemExcludingSubType(iExcludeSubType, oTarget, oItem, oCaster); // delete the local int for forced execution DeleteLocalInt(oCaster, "frc_ohc"); } const string PRC_CUSTOM_ONHIT_USECOUNT = "prc_custom_onhit_usecount"; const string PRC_CUSTOM_ONHIT_DONOTREMOVE = "prc_custom_onhit_donotremove"; const string PRC_CUSTOM_ONHIT_SIGNATURE = "prc_custom_onhit_signature_"; int HasOnHitUniquePowerOnHit(object oItem) { int bFound = FALSE; itemproperty iProperty = GetFirstItemProperty(oItem); while(GetIsItemPropertyValid(iProperty)) { if (GetItemPropertyType(iProperty) == ITEM_PROPERTY_ONHITCASTSPELL && GetItemPropertySubType(iProperty) == IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER && GetItemPropertyDurationType(iProperty) == DURATION_TYPE_PERMANENT ) { bFound = TRUE; break; } iProperty = GetNextItemProperty(oItem); } return FALSE; } void Add_OnHitUniquePower(object oItem, string sCallerSignature) { int nUseCount; itemproperty iProperty; if (!GetLocalInt(oItem, PRC_CUSTOM_ONHIT_SIGNATURE + sCallerSignature)) //Prevent double-add by same caller { nUseCount = GetLocalInt(oItem, PRC_CUSTOM_ONHIT_USECOUNT); SetLocalInt(oItem, PRC_CUSTOM_ONHIT_USECOUNT, nUseCount+1); if (!nUseCount && HasOnHitUniquePowerOnHit(oItem)) SetLocalInt(oItem, PRC_CUSTOM_ONHIT_DONOTREMOVE, TRUE); //To work properly, all other OnHit properties must be added a after the OnHit: Unique Power property-- //Make sure this is the case by removing all of them and adding them back properly. //This is necessary because only the first OnHit property actually fires; //ours will fire the others, but the others won't allow ours to fire unless ours is first. iProperty = GetFirstItemProperty(oItem); while(GetIsItemPropertyValid(iProperty)) { if (GetItemPropertyType(iProperty) == ITEM_PROPERTY_ONHITCASTSPELL && GetItemPropertyDurationType(iProperty) == DURATION_TYPE_PERMANENT ) { RemoveItemProperty(oItem, iProperty); if (GetItemPropertySubType(iProperty) != IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER) DelayCommand(0.0, AddItemProperty(GetItemPropertyDurationType(iProperty), iProperty, oItem)); } iProperty = GetNextItemProperty(oItem); } //Add our On Hit Cast Spell: Unique Power property (which will fire the others as well) iProperty = ItemPropertyOnHitCastSpell(IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER, 1); IPSafeAddItemProperty(oItem, iProperty); SetLocalInt(oItem, PRC_CUSTOM_ONHIT_SIGNATURE + sCallerSignature, TRUE); } } void Remove_OnHitUniquePower(object oItem, string sCallerSignature) { int nUseCount; itemproperty iProperty; if (GetLocalInt(oItem, PRC_CUSTOM_ONHIT_SIGNATURE + sCallerSignature)) //Prevent remove for caller that didn't add { nUseCount = GetLocalInt(oItem, PRC_CUSTOM_ONHIT_USECOUNT); SetLocalInt(oItem, PRC_CUSTOM_ONHIT_USECOUNT, nUseCount-1); if (nUseCount == 1 && !GetLocalInt(oItem, PRC_CUSTOM_ONHIT_DONOTREMOVE)) { iProperty = GetFirstItemProperty(oItem); while(GetIsItemPropertyValid(iProperty)) { if (GetItemPropertyType(iProperty) == ITEM_PROPERTY_ONHITCASTSPELL && GetItemPropertySubType(iProperty) == IP_CONST_ONHIT_CASTSPELL_ONHIT_UNIQUEPOWER && GetItemPropertyDurationType(iProperty) == DURATION_TYPE_PERMANENT ) RemoveItemProperty(oItem, iProperty); iProperty = GetNextItemProperty(oItem); } } SetLocalInt(oItem, PRC_CUSTOM_ONHIT_SIGNATURE + sCallerSignature, FALSE); } }