ff_u_area_gc
Area garbage collector.
Items lying on the ground, dead creatures and body bags are destroyed, with their inventory, once they have been tracked for GC_AREA_DURATION real-time minutes.
An object is tracked from the first scan of its area that finds it. An area is scanned a few seconds after an object enters or leaves it, or an item is lost (e.g. dropped) in it. An item that is picked up is no longer tracked: when it is dropped again, its age starts over. Old objects are looked for about every real-time minute, so an object can outlast its maximum age by up to about a minute and a half.
Variables
ENABLE__FF_U_AREA_GC: (string) (opt-out) Set to "N" on module to disable the unit.
GC_AREA_DURATION: (int) Maximum age, in real-time minutes, before a tracked object is destroyed. Read from the object, its area, then the module. Defaults to 20, at least 1.
Source code
// @code
#include "ff_i_core"
const string ENABLE__FF_U_AREA_GC = "ENABLE__FF_U_AREA_GC";
const string FF_U_AREA_GC__TIMESTAMP = "__FF_U_AREA_GC__TIMESTAMP"; // Time the object started being tracked
const string ARRAY_NAME = "__FF_U_AREA_GC__ARRAY"; // Object array of the tracked objects
const string GC_AREA_DURATION = "GC_AREA_DURATION";
const int DEFAULT = 20; // Real-time minutes
const string FF_U_AREA_GC_FLAG = "__FF_U_AREA_GC_FLAG"; // Delay flag of pending scans and purges
// -----------------------------------------------------------------------------
//! @brief Start tracking oObject, if not already tracked.
//! @param oObject The item.
void _TrackItem(object oObject)
{
if (GetLocalInt(oObject, FF_U_AREA_GC__TIMESTAMP))
return;
LogDebug(__FILE__, "Tracking " + GetObjectInfo(oObject) + " in " + GetObjectInfo(GetArea(oObject)));
ObjectArrayAdd(ARRAY_NAME, oObject);
SetLocalInt(oObject, FF_U_AREA_GC__TIMESTAMP, GetTimeStamp());
}
// -----------------------------------------------------------------------------
//! @brief Start tracking oObject for area garbage collection if eligible.
//! @param oObject An item, creature, or BodyBag placeable.
void TrackItem(object oObject);
void TrackItem(object oObject)
{
if (!GetIsObjectValid(oObject))
return;
object oPossessor = GetItemPossessor(oObject);
int nType = GetObjectType(oObject);
// Item on the ground
if (nType == OBJECT_TYPE_ITEM && !GetIsObjectValid(oPossessor))
_TrackItem(oObject);
// Dead creature
else if (nType == OBJECT_TYPE_CREATURE && GetIsDead(oObject))
_TrackItem(oObject);
// Body bag
else if (nType == OBJECT_TYPE_PLACEABLE && (GetTag(oObject) == TAG_BODY_BAG))
_TrackItem(oObject);
}
// -----------------------------------------------------------------------------
//! @brief Track the eligible objects of oArea, if a scan is pending.
//! @param oArea An area.
void _TrackItems(object oArea)
{
if (!GetIsObjectValid(oArea))
return;
if (!GetDelayFlag(oArea, FF_U_AREA_GC_FLAG))
return;
object oItem = GetFirstObjectInArea(oArea, OBJECT_TYPE_ITEM | OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE);
while (GetIsObjectValid(oItem))
{
TrackItem(oItem);
oItem = GetNextObjectInArea(oArea, OBJECT_TYPE_ITEM | OBJECT_TYPE_CREATURE | OBJECT_TYPE_PLACEABLE);
}
}
// -----------------------------------------------------------------------------
//! @brief Scan oArea for eligible objects in 5 seconds; repeated calls collapse into one.
//! @param oArea An area.
void TrackItems(object oArea);
void TrackItems(object oArea)
{
// Scan at most every 5 seconds.
SetDelayFlag(oArea, FF_U_AREA_GC_FLAG);
DelayCommand(5.0f, _TrackItems(oArea));
}
// -----------------------------------------------------------------------------
//! @brief Remove oObject from area GC tracking.
//! @param oObject A tracked object.
void UntrackItem(object oObject);
void UntrackItem(object oObject)
{
if (!GetIsObjectValid(oObject))
return;
ObjectArrayDelete(ARRAY_NAME, oObject);
DeleteLocalInt(oObject, FF_U_AREA_GC__TIMESTAMP);
}
// -----------------------------------------------------------------------------
//! @brief Destroy tracked objects older than their maximum age, if a purge is pending.
void _OnTimer();
void _OnTimer()
{
object oModule = GetModule();
int nNow = GetTimeStamp();
if (!GetDelayFlag(oModule, FF_U_AREA_GC_FLAG))
return;
sqlquery sqlQuery = ObjectArrayIterate(ARRAY_NAME);
while (SqlStep(sqlQuery))
{
string sUUID = SqlGetString(sqlQuery, 0);
object oItem = GetObjectByUUID(sUUID);
if (!GetIsObjectValid(oItem))
{
ObjectArrayDelete(ARRAY_NAME, oItem);
continue;
}
int nStart = GetLocalInt(oItem, FF_U_AREA_GC__TIMESTAMP);
if (nStart == 0)
{
TrackItem(oItem);
continue;
}
int nMaxAge = GetRecursiveInt(oItem, GC_AREA_DURATION, DEFAULT);
nMaxAge = max(nMaxAge, 1);
nMaxAge = nMaxAge * 60; // Minutes to seconds
int nSeconds = nNow - nStart;
if (nSeconds > 0 && nSeconds >= nMaxAge)
{
LogInfo(__FILE__ + ": Removal of " + GetObjectInfo(oItem)
+ " (age: " + IntToString(nSeconds)
+ ", max age: " + IntToString(nMaxAge) + ")");
ObjectArrayDelete(ARRAY_NAME, oItem);
DestroyInventory(oItem, 0.0f);
DestroyObject(oItem, 0.01f);
}
}
}
// -----------------------------------------------------------------------------
//! @brief Schedule a purge of old objects in 30 seconds; repeated calls collapse into one.
void OnTimer();
void OnTimer()
{
// Purge at most every 30 seconds.
SetDelayFlag(GetModule(), FF_U_AREA_GC_FLAG);
DelayCommand(30.0f, _OnTimer());
}
// -----------------------------------------------------------------------------
void main()
{
string sEvent = NWNX_Events_GetCurrentEvent();
if (sEvent == ON_REGISTER)
{
if (!GetModuleFlag(ENABLE__FF_U_AREA_GC, TRUE))
return;
CreateObjectArray(ARRAY_NAME);
SubscribeToEvent(ON_DELAYED_REGISTER, __FILE__);
SubscribeToEvent(ON_REALTIME_MINUTE, __FILE__);
SubscribeToEvent(ON_DEFAULT_AREA_ENTER, __FILE__);
SubscribeToEvent(ON_DEFAULT_AREA_EXIT, __FILE__);
}
// Avoid the ON_DEFAULT_ITEM_* events during startup.
else if (sEvent == ON_DELAYED_REGISTER)
{
SubscribeToEvent(ON_DEFAULT_ITEM_ACQUIRE, __FILE__);
SubscribeToEvent(ON_DEFAULT_ITEM_LOST, __FILE__);
}
else if (sEvent == ON_DEFAULT_ITEM_ACQUIRE)
UntrackItem(GetModuleItemAcquired());
else if (sEvent == ON_DEFAULT_ITEM_LOST)
TrackItems(GetArea(GetModuleItemLostBy()));
else if (sEvent == ON_DEFAULT_AREA_ENTER || sEvent == ON_DEFAULT_AREA_EXIT)
{
TrackItems(OBJECT_SELF);
_OnTimer();
}
else if (sEvent == ON_REALTIME_MINUTE)
DelayCommand(GetRandomMaxDelay(MINUTE_MAX_DELAY), OnTimer());
}