//:://///////////////////////////////////////////// //:: zep_inc_demi //::////////////////////////////////////////////// /* Constants and functions for use with The Krit's revisions of the CEP adaptation of Demigog's demilich. */ //::////////////////////////////////////////////// //:: Created by: The Krit //:: Created on: May 10, 2007 //::////////////////////////////////////////////// //:: Based on scripts by: Loki Hakanin and Demigog //::////////////////////////////////////////////// #include "colors_inc" #include "zep_inc_scrptdlg" //------------------------------------------------------------------------------ // CONSTANTS //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ // MODULE SETTINGS // The name of a module local integer that holds a number indicating what // should be done with soul gem victims when a demilich is truly destroyed. const string ZEP_DEMI_RESS_VICTIMS = "ZEP_DEMILICH_Raise_Victims"; // There are three possibilities for the content of this variable and // what happens to the victims: // 0 means victims become raisable, but are left dead. // 1 means victims are raised. // 2 means victims are resurrected to full hit points. // The name of a module local string that holds the name of a script to run // when a demilich is killed. const string ZEP_DEMI_DEAD_SCRIPT = "ZEP_DEMILICH_OnDeath_Script"; // Since killing a demilich is not permanent, the standard OnDeath script // is not called when a demilich dies. Instead, the script whose name is held // in this local string is run before the demilich retreats into the Pile of // Bones placeable. // The functions ZEPDemilichGetVictim() and ZEPDemilichGetVictimParty() might // be useful in this script. // The name of a module local string that holds the name of a script to run // when a demilich is truly destroyed. const string ZEP_DEMI_DEST_SCRIPT = "ZEP_DEMILICH_Destroyed_Script"; // The object running this script will be the bone pile placeable. // The PC who destroyed the demilich can be retrieved in this script with // GetLocalObject(OBJECT_SELF, "MyDestroyer"); // The ResRef of the destroyed demilich can be retrieved in this script with // GetLocalString(OBJECT_SELF, "ZEP_DEMI_ResRef"); // Inventory that will be dropped is held by the object retrieved with // GetLocalObject(OBJECT_SELF, "ZEP_DEMI_Holder"); // This script will be run before consuming the holy water and running the // destruction effects. // The functions ZEPDemilichGetVictim() and ZEPDemilichGetVictimParty() might // be useful in this script. // There are two very fast pseudo-heartbeats used by the demilich routines. // The delay on these is the following: const float ZEP_DEMILICH_PSEUDO_DELAY = 6.0; // This can be overridden for the module (even if this script is in a hak) by // setting a local variable on the module. If a module variable's name is // ZEP_DEMILICH_Pseudo_Delay and its type is float, then the contents and that // variable will override the above. // NOTES: A smaller delay means less lag when dealing with the soulgem victims, // but it also means more strain on the server. // The number of seconds required for a demilich to regenerate from battle // can be overridden for a module by storing the custom time as a local // float named ZEP_DEMILICH_Regen_Time on the module. //------------------------------------------------------------------------------ // BLUEPRINT SETTINGS // The minimum level a caster must be before a demilich will consider the // caster worthy of being a soulgem victim can be overridden for an // individual blueprint by storing the custom threshold in a local integer // named ZEP_DEMI_Power_Threshold on the blueprint. // The save DC of the demilich's attempt to trap the souls of casters can be // overridden for an individual blueprint by storing the custom DC in a local // integer named ZEP_DEMI_TrapSoul_SaveDC on the blueprint. //------------------------------------------------------------------------------ // LEGACY SUPPORT // If you want to use the older heartbeat-based demilich, add "zep_demi_bone_hb" // as the OnHeartbeat event of the placeable blueprint zep_demi_skull (Pile of // Bones), and set the following flag to TRUE. const int ZEP_DEMI_USE_LEGACY = FALSE; // IMPORTANT: Do not add a heartbeat event to the blueprint zep_demi_skull0; // just add it to the blueprint without the '0'. // One advantage of using the heartbeat-based demilich is the ability to set // the placeables' "perception" range. Set that here. // The demilich's perception range while resting or regenerating, in meters. // (Setting this higher than 20.0 can be bad.) const float ZEP_DEMI_PERC_RANGE = 5.0; //------------------------------------------------------------------------------ // RESTING/REGENERATION // The ResRefs of the skull and dust placeables to be created when the demilich // goes into resting mode. const string ZEP_DEMI_SKULL_RESREF = "zep_demi_skull"; // Using this draws out the demilich. const string ZEP_DEMI_INERT_RESREF = "zep_demi_skull0"; // Using this starts a conversation. const string ZEP_DEMI_DUST_RESREF = "zep_demi_dust"; // The message spoken by a freshly-regenerated demilich. string ZEP_DEMI_REGEN_MSG = GetStringByStrRef(nZEPDemiRestored); //"At last, I am restored..." // The message spoken by a demilich responding to intruders. string ZEP_DEMI_DIST_MSG = GetStringByStrRef(nZEPDemiDisturbed); //"You disturb my work!" // The number of seconds required for a demilich to regenerate from battle // injuries after seemingly being killed. // Should be at least long enough for a PC to apply Holy Water to the // demilich's bones. const float ZEP_DEMILICH_REGEN_TIME = 300.0; // This can be overridden for a module by storing the custom time as a local // float named ZEP_DEMILICH_Regen_Time on the module. // The tag of the item that is needed to destroy a regenerating demilich. // (By default, this is the tag of Holy Water, whose blueprint is "zep_holy_water".) const string ZEP_DEMI_DEST_TAG = "zep_holy_water"; // The floating text displayed when a demilich is truly destroyed. string ZEP_DEMI_FINAL_DEST = GetStringByStrRef(nZEPDemiVictFree); //"With the demilich destroyed, the souls of its victims are released to their bodies." //------------------------------------------------------------------------------ // SOUL TRAPPING // The maximum number of soulgem victims the demilich will take on at a time // (a.k.a. the number of soulgems embedded in a demilich). const int ZEP_DEMI_NUM_SOULGEMS = 8; // This is not settable via local variables because changing this value in the // middle of a game could be bad. // The minimum level a caster must be before a demilich will consider the // caster worthy of being a soulgem victim. const int ZEP_DEMI_POWER_THRESHOLD = 15; // This can be overridden for an individual blueprint by storing the custom // threshold in a local integer named ZEP_DEMI_Power_Threshold on the blueprint. // The save DC of the demilich's attempt to trap the souls of casters. const int ZEP_DEMI_TRAPSOUL_SAVEDC = 15; // This can be overridden for an individual blueprint by storing the custom // DC in a local integer named ZEP_DEMI_TrapSoul_SaveDC on the blueprint. // The message spoken by a demilich who has been hit by a spell from a high // level caster, just before attempting to capture the caster's soul. string ZEP_DEMI_ONSPELL_MSG = GetStringByStrRef(nZEPDemiHavePower); //"Yes, I sense you have power...your potential shall be mine!" // The combat message sent to a player, as a demilich attempts to capture the // PC's soul. // Will be prefixed by the demilich's name. string ZEP_DEMI_TRAPSOUL_MESSAGE = " attempts Trap the Soul."; // The message that floats over a PC as the PC's soul is stolen. string ZEP_DEMI_TRAPSOUL_FLOATINGTEXT = " has trapped the soul of "; //------------------------------------------------------------------------------ // LOCAL VARIABLES const string ZEP_DEMI_LOCAL_AMBIENT = "ZEP_DEMI_Ambient"; // Eye-candy placeable (dust plume). const string ZEP_DEMI_LOCAL_RESREF = "ZEP_DEMI_ResRef"; // Blueprint of demilich creature. const string ZEP_DEMI_LOCAL_SOURCE = "ZEP_DEMI_Source"; // Placeable storing creature info. const string ZEP_DEMI_LOCAL_HOLDER = "ZEP_DEMI_Holder"; // Inventory storage. const string ZEP_DEMI_LOCAL_SGCORPSE = "ZEP_DEMI_SG_Corpse_";// The corpse of a soul gem victim. // Also: the victim stored on the corpse. const string ZEP_DEMI_LOCAL_HITDICE = "ZEP_DEMI_HitDice"; // The hit dice of the demilich. const string ZEP_DEMI_LOCAL_PARTY = "ZEP_DEMI_Party"; // A party member of a soul gem victim. //------------------------------------------------------------------------------ // PROTOTYPES //------------------------------------------------------------------------------ // Moves all items with the droppable flag set from the inventory of oFrom to // the inventory of oTo. Also moves equipped items. // (The order of the parameters is comparable to an assignment: oTo = oFrom.) void MoveDroppableInventory(object oTo, object oFrom); // Creates and initializes the objects used to represent a resting or // regenerating demilich. // oDemilich is the demilich about to rest or regenerate. // bWasKilled is TRUE if oDemilich was killed (and needs to regenerate). void ZEPDemilichSpawnBones(object oDemilich, int bWasKilled); // Creates a demilich from its resting or regenerating state. // oBones is the bone placeable storing demilich data. // sResRef is the blueprint to use (supports custom demiliches). // bIntrusion is TRUE if the demilich is responding to an intruder. object ZEPDemilichFromBones(object oBones, string sResRef, int bIntrusion); // Creates an area of effect that will serve to detect any nearby intruders. // lTarget is where the effect will be centered. object ZEPDemilichCreateDetector(location lTarget); // Restores a regenerating demilich. // To be run by the bone pile placeable. // sResRef is the blueprint of the demilich. // oDust is the associated dust placeable. // oHolder is the associated inventory holder placeable. void ZEPDemilichRestore(string sResRef, object oDust, object oHolder); // Sees if we want to trap oPC in a soul gem. // If so, returns the gem number to trap oPC within. // If not, returns -1. // To be run by the demilich. int ZEPDemilichChooseSoulGem(object oPC); // Traps the soul of oPC, which kills oPC and prevents resurreaction. // To be run by the demilich. // nGem is the number of the gem in which to trap the soul. void ZEPDemilichTrapSoul(object oPC, int nGem); // Frees the soul trapped in a soulgem, allowing the character ro be raised. // Does nothing if the indicated soulgem does not contain a soul. // To be run by the demilich or the bone pile placeable. // nGem is the number of the soulgem. void ZEPDemilichFreeSoul(int nGem); // Restores oPC's raisable status. // Also raises oPC if ZEP_DEMI_RESS_VICTIMS is set. // To be run by the cloned corpse. // fDelay is the delay that will be used when recursing pseudo-heartbeat style. void ZEPDemilichRaiseVictim(object oPC, float fDelay); // Cleans up the result of oPC's soul being stolen. // Any cutscene-ghost effects are removed. // If the death effect worked, oPC will be made invisible and untargettable, and // a pseudo-heartbeat will be started to track if oPC respawned. // If the death effect failed, the caller is destroyed. // To be called by the cloned corpse. void ZEPDemilichCorpseInit(object oPC); // Pseudo-heartbeat function that will clean-up if a soul gem victim respawns. // To be run by the cloned corpse. // oPC is the real victim. // fDelay is the delay that will be used when recursing pseudo-heartbeat style. void ZEPDemilichCorpseCheck(object oPC, float fDelay); // Retrieves soul gem victim number nGem. // Valid values for nGem are 0 through ZEP_DEMI_NUM_SOULGEMS - 1. // Returns OBJECT_INVALID on error. // To be called by a demilich or the bone pile placeable (as would be the case // if called from an OnDeath or Destruction script). object ZEPDemilichGetVictim(int nGem); // Retrieves a party member of soul gem victim number nGem. // For PC victims, this is a member of the PC's party when the PC was trapped. // For NPC victims, this is the NPC's master. // Valid values for nGem are 0 through ZEP_DEMI_NUM_SOULGEMS - 1. // Returns OBJECT_INVALID on error. // To be called by a demilich or the bone pile placeable (as would be the case // if called from an OnDeath or Destruction script). object ZEPDemilichGetVictimParty(int nGem); //------------------------------------------------------------------------------ // FUNCTIONS //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ // MoveDroppableInventory() // // Moves all items with the droppable flag set from the inventory of oFrom to // the inventory of oTo. Also moves equipped items. // (The order of the parameters is comparable to an assignment: oTo = oFrom.) // void MoveDroppableInventory(object oTo, object oFrom) { // This hangs if oTo is invalid. if ( !GetIsObjectValid(oTo) ) return; // Loop through oFrom's inventory. object oItem = GetFirstItemInInventory(oFrom); while ( oItem != OBJECT_INVALID ) { // Check the droppable flag. if ( GetDroppableFlag(oItem) ) { // Move the item. CopyItem(oItem, oTo, TRUE); DestroyObject(oItem); } // Advance the loop. oItem = GetNextItemInInventory(oFrom); } // Loop through oFrom's equipment slots. int nSlot = NUM_INVENTORY_SLOTS; while ( nSlot-- > 0 ) { oItem = GetItemInSlot(nSlot, oFrom); // Check the droppable flag. if ( GetDroppableFlag(oItem) ) { // Move the item. CopyItem(oItem, oTo, TRUE); DestroyObject(oItem); } } } //------------------------------------------------------------------------------ // ZEPDemilichSpawnBones() // // Creates and initializes the objects used to represent a resting or // regenerating demilich. // oDemilich is the demilich about to rest or regenerate. // bWasKilled is TRUE if oDemilich was killed (and needs to regenerate). // void ZEPDemilichSpawnBones(object oDemilich, int bWasKilled) { // Get the location where the objects will appear. location lDemilich = GetLocation(oDemilich); // Get the blueprint of this demilich. // (Has to be stored in a variable for when this is an OnDeath event.) string sDemilich = GetResRef(oDemilich); // Create a skull pile and dust plume. object oDust = CreateObject(OBJECT_TYPE_PLACEABLE, ZEP_DEMI_DUST_RESREF, lDemilich); object oBones; if ( bWasKilled ) oBones = CreateObject(OBJECT_TYPE_PLACEABLE, ZEP_DEMI_INERT_RESREF, lDemilich); else oBones = CreateObject(OBJECT_TYPE_PLACEABLE, ZEP_DEMI_SKULL_RESREF, lDemilich); // Link the dust to the bones. SetLocalObject(oBones, ZEP_DEMI_LOCAL_AMBIENT, oDust); // Record the blueprint for this demilich. SetLocalString(oBones, ZEP_DEMI_LOCAL_RESREF, sDemilich); // Record the hit dice of this demilich. SetLocalInt(oBones, ZEP_DEMI_LOCAL_HITDICE, GetHitDice(oDemilich)); // Copy the variables recording soulgem victims. int nGem = ZEP_DEMI_NUM_SOULGEMS; while ( nGem-- > 0 ) SetLocalObject(oBones, ZEP_DEMI_LOCAL_SGCORPSE + IntToString(nGem), GetLocalObject(oDemilich, ZEP_DEMI_LOCAL_SGCORPSE + IntToString(nGem))); // See if the demilich is regenerating or merely resting. if ( bWasKilled ) { // Move the droppable inventory into an invisible placeable. object oHolder = CreateObject(OBJECT_TYPE_PLACEABLE, "x0_plc_corpse", lDemilich); MoveDroppableInventory(oHolder, oDemilich); SetUseableFlag(oHolder, FALSE); SetLocalObject(oBones, ZEP_DEMI_LOCAL_HOLDER, oHolder); // Find a suitable delay. float fDelay = GetLocalFloat(GetModule(), "ZEP_DEMILICH_Regen_Time"); if ( fDelay == 0.0 ) fDelay = ZEP_DEMILICH_REGEN_TIME; // Delay-restore the demilich. AssignCommand(oBones, DelayCommand(fDelay, ZEPDemilichRestore(sDemilich, oDust, oHolder))); } // A resting legacy Demilich needs no additional work at this point. else if ( !ZEP_DEMI_USE_LEGACY ) { // Create an object to detect intruders. object oDetector = ZEPDemilichCreateDetector(lDemilich); // Initialize the detector. SetLocalObject(oDetector, ZEP_DEMI_LOCAL_SOURCE, oBones); SetLocalObject(oDetector, ZEP_DEMI_LOCAL_AMBIENT, oDust); SetLocalString(oDetector, ZEP_DEMI_LOCAL_RESREF, sDemilich); // Link the detector to the bone pile. SetLocalObject(oBones, ZEP_DEMI_LOCAL_SOURCE, oDetector); } } //------------------------------------------------------------------------------ // ZEPDemilichFromBones() // // Creates a demilich from its resting or regenerating state. // oBones is the bone placeable storing demilich data. // sResRef is the blueprint to use. // bIntrusion is TRUE if the demilich is responding to an intruder. // object ZEPDemilichFromBones(object oBones, string sResRef, int bIntrusion) { // Double-check the blueprint. if ( sResRef == "" ) // Use the CEP default. sResRef = "zep_demi_lich"; // Create the demilich. object oDemilich = CreateObject(OBJECT_TYPE_CREATURE, sResRef, GetLocation(oBones)); ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_FNF_GAS_EXPLOSION_MIND), oBones); // Copy the variables recording soulgem victims. int nGem = ZEP_DEMI_NUM_SOULGEMS; while ( nGem-- > 0 ) SetLocalObject(oDemilich, ZEP_DEMI_LOCAL_SGCORPSE + IntToString(nGem), GetLocalObject(oBones, ZEP_DEMI_LOCAL_SGCORPSE + IntToString(nGem))); // Have the demilich say something appropriate. string sSayThis = ColorTokenShout(); if ( bIntrusion ) sSayThis += ZEP_DEMI_DIST_MSG; else sSayThis += ZEP_DEMI_REGEN_MSG; sSayThis += ColorTokenEnd(); // A creature apparently will not be heard if told to speak immediately upon // spawning. On my machine, a quarter-second delay worked well, so a full // second should be safe, yet still not noticeable by a player. AssignCommand(oDemilich, DelayCommand(1.0, SpeakString(sSayThis))); // Add a little audial panache. AssignCommand(oDemilich, DelayCommand(0.5, PlaySound("c_demilich_bat2"))); // Return the newly created demilich. return oDemilich; } //------------------------------------------------------------------------------ // ZEPDemilichCreateDetector() // // Creates an area of effect that will serve to detect any nearby intruders. // lTarget is where the effect will be centered. // object ZEPDemilichCreateDetector(location lTarget) { // Create an invisible area of effect to detect intruders. effect eDetector = EffectAreaOfEffect(AOE_PER_CUSTOM_AOE, "zep_demi_aoe_ent"); ApplyEffectAtLocation(DURATION_TYPE_PERMANENT, eDetector, lTarget); // Look for the area of effect object we just created. object oDetector = GetFirstObjectInShape(SHAPE_CUBE, 0.0, lTarget, FALSE, OBJECT_TYPE_AREA_OF_EFFECT); while( GetIsObjectValid(oDetector) ) { // Match creator, tag, and not initialized yet. if( GetAreaOfEffectCreator(oDetector) == OBJECT_SELF && GetTag(oDetector) == "VFX_CUSTOM" && GetLocalObject(oDetector, ZEP_DEMI_LOCAL_SOURCE) == OBJECT_INVALID ) { // Return this object. return oDetector; } // Get the next candidate AOE object. oDetector = GetNextObjectInShape(SHAPE_CUBE, 0.0, lTarget, FALSE, OBJECT_TYPE_AREA_OF_EFFECT); } // This should never happen, but there still needs to be a default return value. return OBJECT_INVALID; } //------------------------------------------------------------------------------ // ZEPDemilichRestore() // // Restores a regenerating demilich. // To be run by the bone pile placeable. // sResRef is the blueprint of the demilich. // oDust is the associated dust placeable. // oHolder is the associated inventory holder placeable. // void ZEPDemilichRestore(string sResRef, object oDust, object oHolder) { // See if the demilich is in the process of being destroyed. if ( GetLocalInt(OBJECT_SELF, "DESTROYED") ) // Abort. return; // Spawn the demilich. ZEPDemilichFromBones(OBJECT_SELF, sResRef, FALSE); // Destroy oHolder's inventory. object oItem = GetFirstItemInInventory(oHolder); while ( oItem != OBJECT_INVALID ) { DestroyObject(oItem); oItem = GetNextItemInInventory(oHolder); } // Destroy the placeables. DestroyObject(oHolder); DestroyObject(oDust); DestroyObject(OBJECT_SELF); } //------------------------------------------------------------------------------ // ZEPDemilichChooseSoulGem() // // Sees if we want to trap oPC in a soul gem. // If so, returns the gem number to trap oPC within. // If not, returns -1. // To be run by the demilich. // int ZEPDemilichChooseSoulGem(object oPC) { // Do not trap myself. if ( oPC == OBJECT_SELF ) return -1; // Make sure oPC is the right type of creature. if ( !GetIsPC(oPC) && !GetLocalInt(oPC, "ZEP_DEMILICH_AllowSoulGem") ) // Do not trap. return -1; // Get the threshold for our attention from the demilich. int nThreshold = GetLocalInt(OBJECT_SELF, "ZEP_DEMI_Power_Threshold"); if ( nThreshold == 0 ) // Use the module default. nThreshold = ZEP_DEMI_POWER_THRESHOLD; // See if oPC is not worthy of attention. if ( GetCasterLevel(oPC) < nThreshold ) // Do not trap. return -1; // Find the weakest entrapped soul. int nWeakestGem = -1; int nWeakestLevel = 99; int nGem = ZEP_DEMI_NUM_SOULGEMS; while ( nGem-- > 0 ) { // Get the level of the prisoner of this soul gem. int nLevel = GetHitDice(GetLocalObject(OBJECT_SELF, ZEP_DEMI_LOCAL_SGCORPSE + IntToString(nGem))); // Check for an empty gem. if ( nLevel == 0 ) // Use this gem. return nGem; // Check for a new lowest level. else if ( nLevel < nWeakestLevel ) { // Remember this gem. nWeakestLevel = nLevel; nWeakestGem = nGem; } } // See if we found a prisoner we would give up for oPC. if ( nWeakestLevel < GetHitDice(oPC) ) // Use the weakest gem. return nWeakestGem; // At this point, it's not worth the effort. Do not trap. return -1; } //------------------------------------------------------------------------------ // ZEPDemilichTrapSoul() // // Traps the soul of oPC, which kills oPC and prevents resurreaction. // To be run by the demilich. // nGem is the number of the gem in which to trap the soul. // void ZEPDemilichTrapSoul(object oPC, int nGem) { float fDelay = 1.5; // If there is an existing prisoner, free it. ZEPDemilichFreeSoul(nGem); // Stop the PC for this effect. (Makes the visual effects look better.) AssignCommand(oPC, ClearAllActions()); DelayCommand(0.1, SetCommandable(FALSE, oPC)); DelayCommand(fDelay, SetCommandable(TRUE, oPC)); // Clone oPC in place. ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectCutsceneGhost(), oPC); object oClone = CopyObject(oPC, GetLocation(oPC), OBJECT_INVALID, "ZEP_DEMILICH_VICTIM"); // The clone will become a selectable, but not raisable, corpse. AssignCommand(oClone, SetIsDestroyable(FALSE, FALSE, TRUE)); // Record the soon-to-be corpse. SetLocalObject(OBJECT_SELF, ZEP_DEMI_LOCAL_SGCORPSE + IntToString(nGem), oClone); SetLocalObject(oClone, ZEP_DEMI_LOCAL_SGCORPSE, oPC); // Apply a visual effect. ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectBeam(VFX_BEAM_HOLY, OBJECT_SELF, BODY_NODE_HAND), oClone, fDelay); // Give some feedback. DelayCommand(fDelay, FloatingTextStringOnCreature( GetName(OBJECT_SELF) + ZEP_DEMI_TRAPSOUL_FLOATINGTEXT + GetName(oPC) + "!", oPC)); // Kill PC and clone. effect oDeath = SupernaturalEffect(EffectDeath()); DelayCommand(fDelay, ApplyEffectToObject(DURATION_TYPE_INSTANT, oDeath, oClone)); DelayCommand(fDelay, ApplyEffectToObject(DURATION_TYPE_INSTANT, oDeath, oPC)); // Turn processing over to the clone. // This will either hide oPC so it cannot be targetted by Raise Dead, or // destroy the clone so there does not appear to be a copy involved. AssignCommand(oClone, DelayCommand(fDelay + 0.1, ZEPDemilichCorpseInit(oPC))); } //------------------------------------------------------------------------------ // ZEPDemilichFreeSoul() // // Frees the soul trapped in a soulgem, allowing the character ro be raised. // Does nothing if the indicated soulgem does not contain a soul. // To be run by the demilich or the bone pile placeable. // nGem is the number of the soulgem. // void ZEPDemilichFreeSoul(int nGem) { // Retrieve and delete the relevant local variable. object oCorpse = GetLocalObject(OBJECT_SELF, ZEP_DEMI_LOCAL_SGCORPSE + IntToString(nGem)); DeleteLocalObject(OBJECT_SELF, ZEP_DEMI_LOCAL_SGCORPSE + IntToString(nGem)); // See if there is a soul currently trapped in the soulgem. if ( !GetIsObjectValid(oCorpse) ) // No soul to free, nothing to do. return; // Get the PC whose soul is being released. object oPC = GetLocalObject(oCorpse, ZEP_DEMI_LOCAL_SGCORPSE); // Generate visual effects. float fVFXDuration = 0.4 + 0.07 * GetDistanceToObject(oCorpse); // Use an auxiliary placeable so that the visuals can overlap, and because // faked spells don't fire reliably. object oVFXMaker = CreateObject(OBJECT_TYPE_PLACEABLE, "x0_plc_bomb", GetLocation(OBJECT_SELF)); AssignCommand(oVFXMaker, ActionCastSpellAtObject(SPELL_PHANTASMAL_KILLER, oCorpse)); AssignCommand(oVFXMaker, ActionDoCommand(DestroyObject(oVFXMaker))); // Visual on the corpse. AssignCommand(oCorpse, DelayCommand(fVFXDuration, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_RESTORATION_GREATER), oCorpse))); // Make the corpse disappear (in case the victim logged out). AssignCommand(oCorpse, DelayCommand(fVFXDuration + 1.0, ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectVisualEffect(VFX_DUR_CUTSCENE_INVISIBILITY), oCorpse))); // Get the pseudo-heartbeat delay. float fDelay = GetLocalFloat(GetModule(), "ZEP_DEMILICH_Pseudo_Delay"); if ( fDelay == 0.0 ) // Use the default. fDelay = ZEP_DEMILICH_PSEUDO_DELAY; // Restore the PC. // Delayed to give the visual effect time to execute. AssignCommand(oCorpse, DelayCommand(fVFXDuration + 0.1, ZEPDemilichRaiseVictim(oPC, fDelay))); } //------------------------------------------------------------------------------ // void ZEPDemilichRaiseVictim() // // Restores oPC's raisable status. // Also raises oPC if ZEP_DEMI_RESS_VICTIMS is set. // To be run by the cloned corpse. // fDelay is the delay that will be used when recursing pseudo-heartbeat style. // void ZEPDemilichRaiseVictim(object oPC, float fDelay) { // See if target is invalid. if ( !GetIsObjectValid(oPC) ) { // Player must have logged out. // Search again next round. DelayCommand(fDelay, ZEPDemilichRaiseVictim(oPC, fDelay)); return; } // Make oPC visible again. // Loop through active effects. effect eInvis = GetFirstEffect(oPC); while ( GetIsEffectValid(eInvis) ) { // Check for the cutscene invisibility. if ( GetEffectCreator(eInvis) == OBJECT_SELF ) // Remove this effect. RemoveEffect(oPC, eInvis); // Update the loop. eInvis = GetNextEffect(oPC); } // Send a message to oPC to explain the situation. SendMessageToPC(oPC, GetStringByStrRef(nZEPReturnToLife)); // "You feel disoriented momentarily as your soul returns to its mortal shell." // Check the ZEP_DEMI_RESS_VICTIMS flag. int nResVictims = GetLocalInt(GetModule(), ZEP_DEMI_RESS_VICTIMS); if ( nResVictims > 0 ) { // Raise the vicitm. ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectResurrection(), oPC); // Check for full resurrection. if ( nResVictims > 1 ) // Heal the victim to full hit points. ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectHeal(GetMaxHitPoints(oPC)), oPC); } // This corpse is no longer needed. SetIsDestroyable(TRUE, FALSE, FALSE); DestroyObject(OBJECT_SELF); } //------------------------------------------------------------------------------ // ZEPDemilichCorpseInit() // // Cleans up the result of oPC's soul being stolen. // Any cutscene-ghost effects are removed. // If the death effect worked, oPC will be made invisible and untargettable, and // a pseudo-heartbeat will be started to track if oPC respawned. // If the death effect failed, the caller is destroyed. // To be called by the cloned corpse. // void ZEPDemilichCorpseInit(object oPC) { // Make the corpse cutscene-ghosted. (Not sure if this helps, but it might.) ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectCutsceneGhost(), OBJECT_SELF); // Loop through active effects on oPC. effect eGhost = GetFirstEffect(oPC); while ( GetIsEffectValid(eGhost) ) { // Look for cutscene-ghost effects. if ( GetEffectType(eGhost) == EFFECT_TYPE_CUTSCENEGHOST ) // Remove cutscene-ghost. RemoveEffect(oPC, eGhost); eGhost = GetNextEffect(oPC); } if ( GetIsDead(oPC) ) { // Hide oPC with cutscene invisibility. // Effect is extraordinary so that it cannot be dispelled, but can be gotten // rid of by the PC (by resting) if something goes wrong with the removal scripts. ApplyEffectToObject(DURATION_TYPE_PERMANENT, ExtraordinaryEffect(EffectVisualEffect(VFX_DUR_CUTSCENE_INVISIBILITY)), oPC); // Prevent party members from targetting the character's portrait. if ( GetIsPC(oPC) ) { // First, find a party member who is not oPC, if any. object oParty = GetFirstFactionMember(oPC); if ( oParty == oPC ) oParty = GetNextFactionMember(oPC); // Keep a record of oPC's party in case the module wants it later. SetLocalObject(OBJECT_SELF, ZEP_DEMI_LOCAL_PARTY, oParty); // Now remove oPC from the party so others can't target the portrait. RemoveFromParty(oPC); } else { // See if oPC has a master. object oMaster = GetMaster(oPC); if ( oMaster != OBJECT_INVALID ) { // Keep a record of oPC's master in case the module wants it later. SetLocalObject(OBJECT_SELF, ZEP_DEMI_LOCAL_PARTY, oMaster); // Now remove oPC from the party so others can't target the portrait. // NOTE: This function is called a split second after the death // effect is applied, so any module-specific code will have a chance // to run before this line fires the henchman. RemoveHenchman(GetMaster(oPC), oPC); } } } // This clone corpse should not drop any items. // Clear out inventory. object oItem = GetFirstItemInInventory(); while ( oItem != OBJECT_INVALID ) { DestroyObject(oItem); oItem = GetNextItemInInventory(); } // Flag equipped items as undroppable. int nSlot = NUM_INVENTORY_SLOTS; while ( nSlot-- > 0 ) SetDroppableFlag(GetItemInSlot(nSlot), FALSE); // Remove gold. TakeGoldFromCreature(GetGold(), OBJECT_SELF, TRUE); // Get the pseudo-heartbeat delay. float fDelay = GetLocalFloat(GetModule(), "ZEP_DEMILICH_Pseudo_Delay"); if ( fDelay == 0.0 ) // Use the default. fDelay = ZEP_DEMILICH_PSEUDO_DELAY; // Start a pseudo-heartbeat that will destroy the caller when oPC is alive. ZEPDemilichCorpseCheck(oPC, fDelay); } //------------------------------------------------------------------------------ // ZEPDemilichCorpseCheck() // // Pseudo-heartbeat function that will clean-up if a soul gem victim respawns. // To be run by the cloned corpse. // oPC is the real victim. // fDelay is the delay that will be used when recursing pseudo-heartbeat style. // void ZEPDemilichCorpseCheck(object oPC, float fDelay) { // See if oPC is still dead (or logged off). if ( GetIsDead(oPC) || !GetIsObjectValid(oPC) ) // Recurse the pseudo-heartbeat. DelayCommand(fDelay, ZEPDemilichCorpseCheck(oPC, fDelay)); else { // oPC is alive! Hooray! // Since oPC was made cutscene-invisible, this can only happen via // respawning, DM intervention, or release from the demilich. // Make oPC visible again. // Loop through active effects. effect eInvis = GetFirstEffect(oPC); while ( GetIsEffectValid(eInvis) ) { // Check for the cutscene invisibility. if ( GetEffectCreator(eInvis) == OBJECT_SELF ) // Remove this effect. RemoveEffect(oPC, eInvis); // Update the loop. eInvis = GetNextEffect(oPC); } // This corpse is no longer needed. SetIsDestroyable(TRUE, FALSE, FALSE); DestroyObject(OBJECT_SELF); } } //------------------------------------------------------------------------------ // ZEPDemilichGetVictim() // // Retrieves soul gem victim number nGem. // // Valid values for nGem are 0 through ZEP_DEMI_NUM_SOULGEMS - 1. // Returns OBJECT_INVALID on error. // // To be called by a demilich or the bone pile placeable (as would be the case // if called from an OnDeath or Destruction script). // object ZEPDemilichGetVictim(int nGem) { return GetLocalObject( GetLocalObject(OBJECT_SELF, ZEP_DEMI_LOCAL_SGCORPSE + IntToString(nGem)), ZEP_DEMI_LOCAL_PARTY); } //------------------------------------------------------------------------------ // ZEPDemilichGetVictimParty() // // Retrieves a party member of soul gem victim number nGem. // For PC victims, this is a member of the PC's party when the PC was trapped. // For NPC victims, this is the NPC's master. // // Valid values for nGem are 0 through ZEP_DEMI_NUM_SOULGEMS - 1. // Returns OBJECT_INVALID on error. // // To be called by a demilich or the bone pile placeable (as would be the case // if called from an OnDeath or Destruction script). // object ZEPDemilichGetVictimParty(int nGem) { return GetLocalObject( GetLocalObject(OBJECT_SELF, ZEP_DEMI_LOCAL_SGCORPSE + IntToString(nGem)), ZEP_DEMI_LOCAL_SGCORPSE); }