// anewonexit.nss
// WoG's general area OnExit handler. This handles the cleanup tasks and
// calls the script that lets monsters rest (forces them to, really).
#include "nw_i0_plot"
const string LV_SPAWNS_LEFT = "WoG_MySpawnsLeft";
// :: Delayed Cleanup for remains of familiars and animal companions ::
// This is the tag the cleanup script will know denotes a fallen familiar/companion.
const string ASSOCIATE_MARKER_TAG = "AssociateRemains"; // must match nw_ch_ac7.nss
// Only reschedule associate cleanup once.
const string ASSOCIATE_CLEANUP_SCHEDULED = "ASSOCIATE_CLEANUP_SCHEDULED";
// The associate cleanup rescheduling delay, in turns (minutes).
// This can also be set as local int ASSOCIATE_CLEANUP_DELAY on the area.
const int DEFAULT_ASSOCIATE_CLEANUP_DELAY = 20; // one game hour
// The delayed function that implements the cleanup of oArea.
void DoCleanup(object oArea);
// Destroys an encounter creature, possibly triggering an encounter to reactivate.
void DestroyEncounterCreature(object oCreature);
// Implements an encounter resetting.
void EncounterResetter(object oEncounter);
// Destroys container (like the remains of a familiar or animal companion).
void DestroyContainer(object oRemains);
void main()
{
object oArea = OBJECT_SELF;
// Only fire for PCs.
//if ( !GetIsPC(GetExitingObject()) )
// return;
// Cancel the PC check. The reason I got rid of the player count in a local
// variable is that there are some cases where OnExit fails to fire (e.g.
// a logout is reported to cause this in at least some cases). If we miss
// a PC exiting the area, we should grab the next opportunity to make up
// for it. --TK
// But we can still abort if the area is clean.
if ( !GetLocalInt(oArea, "WoG_Area_Entered") )
return;
// Look for a living PC in the area. (Ignore dead PCs for now -- they do not
// stop the part where monsters rest.)
object oPC = GetFirstObjectInArea(oArea);
if ( !GetIsPlayerCharacter(oPC) || GetIsDead(oPC) )
oPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oPC, 1,
CREATURE_TYPE_IS_ALIVE, TRUE);
// If there is a living PC in the area, do nothing more.
if ( oPC != OBJECT_INVALID )
return;
// Schedule a cleanup.
// (This is measured in seconds.)
int nTimeout = GetLocalInt(oArea, "CleanupDelay");
if ( nTimeout == 0 )
// Default to 30 seconds, to accommodate an accidental out & in.
nTimeout = 30;
DelayCommand(IntToFloat(nTimeout), DoCleanup(oArea));
// Also let the monsters rest, particularly the non-encounter creatures.
ExecuteScript("onexitformonster", oArea);
}
// -----------------------------------------------------------------------------
// The delayed function that implements the cleanup of oArea.
void DoCleanup(object oArea)
{
// Look for a living or dead PC in the area.
// (This ignores a player who had entered and left during the delay, but
// tracking that may involve more overhead than it is worth.)
object oPC = GetFirstObjectInArea(oArea);
if ( !GetIsPlayerCharacter(oPC) )
{
// Remember the first object in the area in case we need to do a second search.
object oReference = oPC;
oPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oReference, 1,
CREATURE_TYPE_IS_ALIVE, TRUE);
if ( oPC == OBJECT_INVALID )
oPC = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, oReference, 1,
CREATURE_TYPE_IS_ALIVE, FALSE);
}
// No cleanup while a PC is in the area.
if ( oPC != OBJECT_INVALID )
return;
// Get the delay in cleaning up associate remains
int CleanupDelay = GetLocalInt(oArea,"ASSOCIATE_CLEANUP_DELAY");
if ( CleanupDelay == 0 )
CleanupDelay = DEFAULT_ASSOCIATE_CLEANUP_DELAY;
// Loop through all objects in the area.
int nType;
string sLowerName;
object oLoop = GetFirstObjectInArea(oArea);
while ( oLoop != OBJECT_INVALID )
{
nType = GetObjectType(oLoop);
sLowerName = GetStringLowerCase(GetName(oLoop));
// Determine the type of cleanup.
if ( nType == OBJECT_TYPE_CREATURE )
{
// Destroy encounter creatures; leave placed creatures alone.
// Avoid destroying encounter creatures dominated by PCs.
if ( GetIsEncounterCreature(oLoop) && !GetIsPlayerCharacter(GetMaster(oLoop)) )
DestroyEncounterCreature(oLoop);
}
else if ( nType == OBJECT_TYPE_ENCOUNTER )
{
// Check for a reset once the current script is done.
AssignCommand(oArea, EncounterResetter(oLoop));
}
else if ( nType == OBJECT_TYPE_ITEM || sLowerName == "remains" ||
sLowerName == "corpse" )
{
// Clean up items and the contents of corpses and remains.
if ( GetHasInventory(oLoop) )
{
// If cleanup has already been rescheduled, move on
if ( GetLocalInt(oLoop, ASSOCIATE_CLEANUP_SCHEDULED) )
{
oLoop = GetNextObjectInArea(oArea);
continue;
}
// If this is the remains of a fallen familiar, delay cleanup
if ( HasItem(oLoop, ASSOCIATE_MARKER_TAG) )
{
DelayCommand(TurnsToSeconds(CleanupDelay), DestroyContainer(oLoop));
SetLocalInt(oLoop, ASSOCIATE_CLEANUP_SCHEDULED, 1);
oLoop = GetNextObjectInArea(oArea);
continue;
}
// Destroy the inventory.
object oItem = GetFirstItemInInventory(oLoop);
while ( GetIsObjectValid(oItem) )
{
DestroyObject(oItem);
oItem = GetNextItemInInventory(oLoop);
}
}
if ( nType == OBJECT_TYPE_ITEM )
{
// Items get destroyed.
DestroyObject(oLoop);
}
}
// Update the loop.
oLoop = GetNextObjectInArea(oArea);
}
// The area is now clean.
DeleteLocalInt(OBJECT_SELF, "WoG_Area_Entered");
}
// Destroys an encounter creature, possibly triggering an encounter to reactivate.
void DestroyEncounterCreature(object oCreature)
{
// See if we can trace this creature back to its encounter.
object oEncounter = GetLocalObject(oCreature, "WoG_MySpawner");
if ( oEncounter != OBJECT_INVALID )
// Increase the encounter's count of creatures left.
SetLocalInt(oEncounter, LV_SPAWNS_LEFT,
GetLocalInt(oEncounter, LV_SPAWNS_LEFT) + 1);
// Now destroy the creature.
DestroyObject(oCreature);
}
// Implements an encounter resetting.
void EncounterResetter(object oEncounter)
{
int nTotal = GetLocalInt(oEncounter, "WoG_MySpawns");
int nRemaining = GetLocalInt(oEncounter, LV_SPAWNS_LEFT);
// See if insufficient creatures had been killed before the cleanup.
if ( nRemaining > nTotal/4 )
// Reset this (after the creatures disappear).
DelayCommand(10.0, SetEncounterActive(TRUE, oEncounter));
// Clean up the local variables.
DeleteLocalInt(oEncounter, "WoG_MySpawns");
DeleteLocalInt(oEncounter, LV_SPAWNS_LEFT);
}
// Destroys container (like the remains of a familiar or animal companion).
void DestroyContainer(object oContainer)
{
// Quit if it's already gone
if ( !GetIsObjectValid(oContainer) )
{
return;
}
// Destroy the inventory.
object oItem = GetFirstItemInInventory(oContainer);
while ( GetIsObjectValid(oItem) )
{
DestroyObject(oItem);
oItem = GetNextItemInInventory(oContainer);
}
if ( GetObjectType(oContainer) == OBJECT_TYPE_ITEM )
{
DestroyObject(oContainer);
}
}