884 lines
35 KiB
Plaintext
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);
|
|
}
|
|
|