LoT_PRC8/_module/nss/zep_inc_demi.nss
Jaysyn904 ec287507a1 Initial upload
Initial upload.
2023-09-25 21:32:17 -04:00

884 lines
35 KiB
Plaintext

//::///////////////////////////////////////////////
//:: 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);
}