tinymux/mux/modules/engine/object.cpp
Stephen Dennis 7d7ea2ea79 Fix bulk deletion mod_count: monotonic increment, no sentinel
The UINT32_MAX sentinel wrapped to 0 on next write, breaking
monotonicity. Replace with:

- attr_mod_count_invalidate_object() now seeds all attr counters
  from SQLite BEFORE deletion (via GetAllAttrModCounts), then
  increments each by 1. Counters are strictly monotonic — no
  sentinel, no wrapping, no reuse across delete/recreate cycles.

- Call sites in object.cpp moved BEFORE DelAllAttributes so the
  SQLite rows are still readable when seeding.

- ClearAttributes() clears the in-memory map entirely (correct
  for full database reload where JIT cache is also cleared).

Smoke tests: 610/610 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 21:25:50 -06:00

2303 lines
60 KiB
C++

/*! \file object.cpp
* \brief Low-level object manipulation routines.
*
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
#include "sqlite_backend.h"
#include "engine_api.h"
#define IS_CLEAN(i) (isGarbage(i) && Going(i) && \
((i) >= 0) && ((i) < mudstate.db_top) && \
(Location(i) == NOTHING) && \
(Contents(i) == NOTHING) && (Exits(i) == NOTHING) && \
(Next(i) == NOTHING) && (Owner(i) == GOD))
static int check_type;
// Best-effort refresh of SQLite attributes from the in-memory attribute cache.
// This intentionally avoids clearing the entire attribute table because the
// cache can be incomplete.
//
static bool sqlite_refresh_cached_attributes_in_sqlite(void)
{
struct AttrRow
{
dbref obj;
int attrnum;
std::vector<UTF8> value;
dbref owner;
int flags;
};
// Use only in-memory cache entries as source of truth here. This avoids
// pulling values from SQLite while we are trying to repair SQLite state.
//
std::vector<AttrRow> snapshot;
snapshot.reserve(mudstate.attribute_lru_cache_map.size());
for (const auto& kv : mudstate.attribute_lru_cache_map)
{
const Aname& key = kv.first;
const auto& entry = kv.second;
if ( key.attrnum <= 0
|| key.attrnum == A_LIST
|| key.object < 0
|| key.object >= mudstate.db_top
|| isGarbage(key.object))
{
continue;
}
ATTR *pAttr = atr_num(key.attrnum);
if ( nullptr == pAttr
|| (pAttr->flags & AF_DELETED))
{
continue;
}
if (entry.data.empty())
{
Log.tinyprintf(T("sqlite_refresh_cached_attributes_in_sqlite: invalid empty cache entry #%d/%d" ENDLINE),
key.object, key.attrnum);
return false;
}
AttrRow row;
row.obj = key.object;
row.attrnum = key.attrnum;
row.owner = entry.attr_owner;
row.flags = entry.attr_flags;
row.value = entry.data;
snapshot.push_back(std::move(row));
}
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
if (!sqldb.Begin())
{
return false;
}
for (const auto& row : snapshot)
{
if (!sqldb.PutAttribute(row.obj,
row.attrnum,
row.value.empty() ? nullptr : row.value.data(),
row.value.size(),
row.owner,
row.flags))
{
sqldb.Rollback();
return false;
}
}
if (!sqldb.Commit())
{
sqldb.Rollback();
return false;
}
return true;
}
// Clear SQLite tables/meta after failed create-object reconcile attempts.
//
static bool sqlite_clear_after_reconcile_failure(CSQLiteDB &sqldb)
{
if (!sqldb.Begin())
{
sqldb.Rollback();
if (!sqldb.Begin())
{
return false;
}
}
if ( !sqldb.ClearAttributes()
|| !sqldb.ClearObjectTable()
|| !sqldb.ClearAttrNames()
|| !sqldb.PutMeta("attr_next", A_USER_START)
|| !sqldb.PutMeta("db_top", 0)
|| !sqldb.PutMeta("record_players", 0)
|| !sqldb.Commit())
{
sqldb.Rollback();
return false;
}
return true;
}
// Force a newly-created object slot back to a reusable clean shape even
// if backend attr deletes fail during teardown.
//
static void force_reclaim_failed_create_slot(dbref obj)
{
if ( obj < 0
|| obj >= mudstate.db_top)
{
return;
}
if (db[obj].name)
{
if (mudconf.cache_names)
{
if (db[obj].name == db[obj].purename)
{
db[obj].purename = nullptr;
}
if (db[obj].name == db[obj].moniker)
{
db[obj].moniker = nullptr;
}
}
MEMFREE(db[obj].name);
db[obj].name = nullptr;
}
if (mudconf.cache_names)
{
if (db[obj].purename)
{
MEMFREE(db[obj].purename);
db[obj].purename = nullptr;
}
if (db[obj].moniker)
{
MEMFREE(db[obj].moniker);
db[obj].moniker = nullptr;
}
}
db[obj].fs.word[FLAG_WORD1] = TYPE_GARBAGE | GOING;
db[obj].fs.word[FLAG_WORD2] = 0;
db[obj].fs.word[FLAG_WORD3] = 0;
db[obj].powers = 0;
db[obj].powers2 = 0;
db[obj].location = NOTHING;
db[obj].contents = NOTHING;
db[obj].exits = NOTHING;
db[obj].next = NOTHING;
db[obj].owner = GOD;
db[obj].parent = NOTHING;
db[obj].zone = NOTHING;
db[obj].cpu_time_used.Set100ns(0);
db[obj].tThrottleExpired.Set100ns(0);
s_ThAttrib(obj, 0);
s_ThMail(obj, 0);
s_ThRefs(obj, 0);
mudstate.bfListens.Clear(obj);
mudstate.bfCommands.Clear(obj);
mudstate.bfNoListens.Clear(obj);
mudstate.bfNoCommands.Clear(obj);
// Link into freelist directly without write-through.
//
db[obj].link = mudstate.freelist;
mudstate.freelist = obj;
}
// Best-effort backend cleanup for objects that failed creation and were
// reclaimed locally.
//
static void reconcile_failed_create_backend(dbref obj)
{
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
bool bCleaned = false;
if (sqldb.Begin())
{
CSQLiteDB::ObjectRecord rec;
rec.dbref_val = obj;
rec.location = db[obj].location;
rec.contents = db[obj].contents;
rec.exits = db[obj].exits;
rec.next = db[obj].next;
rec.link = db[obj].link;
rec.owner = db[obj].owner;
rec.parent = db[obj].parent;
rec.zone = db[obj].zone;
rec.pennies = Pennies(obj);
rec.flags1 = db[obj].fs.word[FLAG_WORD1];
rec.flags2 = db[obj].fs.word[FLAG_WORD2];
rec.flags3 = db[obj].fs.word[FLAG_WORD3];
rec.powers1 = db[obj].powers;
rec.powers2 = db[obj].powers2;
// Seed in-memory mod_counts BEFORE deleting SQLite rows.
attr_mod_count_invalidate_object(obj);
if ( sqldb.DeleteObject(obj)
&& sqldb.DelAllAttributes(obj)
&& sqldb.InsertObject(rec)
&& sqldb.PutMeta("db_top", mudstate.db_top)
&& sqldb.Commit())
{
bCleaned = true;
}
else
{
sqldb.Rollback();
}
}
if (!bCleaned)
{
bool bAttrsCleared = false;
if (sqldb.Begin())
{
// Seed in-memory mod_counts BEFORE deleting SQLite rows.
attr_mod_count_invalidate_object(obj);
if (sqldb.DelAllAttributes(obj) && sqldb.Commit())
{
bAttrsCleared = true;
}
else
{
sqldb.Rollback();
}
}
STARTLOG(LOG_ALWAYS, "DB", "OBJSYNC");
if (!bAttrsCleared)
{
log_text(T("create_obj failed-cleanup fallback: attribute cleanup incomplete; attempting cached attribute refresh + sqlite_sync_runtime."));
}
else
{
log_text(T("create_obj failed-cleanup fallback: attempting cached attribute refresh + sqlite_sync_runtime."));
}
ENDLOG;
bool bRecovered = sqlite_refresh_cached_attributes_in_sqlite();
bRecovered = bRecovered && sqlite_sync_runtime();
if (!bRecovered)
{
if (!sqlite_clear_after_reconcile_failure(sqldb))
{
STARTLOG(LOG_ALWAYS, "DB", "OBJSYNC");
log_text(T("create_obj failed-cleanup fallback SQLite clear failed."));
ENDLOG;
}
STARTLOG(LOG_ALWAYS, "DB", "OBJSYNC");
log_text(T("create_obj failed-cleanup fallback cached refresh/sync failed."));
ENDLOG;
}
}
}
/*
* ---------------------------------------------------------------------------
* * Log_pointer_err, Log_header_err, Log_simple_damage: Write errors to the
* * log file.
*/
static void Log_pointer_err(dbref prior, dbref obj, dbref loc, dbref ref,
const UTF8 *reftype, const UTF8 *errtype)
{
STARTLOG(LOG_PROBLEMS, "OBJ", "DAMAG");
log_type_and_name(obj);
if (loc != NOTHING)
{
log_text(T(" in "));
log_type_and_name(loc);
}
log_text(T(": "));
if (prior == NOTHING)
{
log_text(reftype);
}
else
{
log_text(T("Next pointer"));
}
log_text(T(" "));
log_type_and_name(ref);
log_text(T(" "));
log_text(errtype);
ENDLOG;
}
static void Log_header_err(dbref obj, dbref loc, dbref val, bool is_object,
const UTF8 *valtype, const UTF8 *errtype)
{
STARTLOG(LOG_PROBLEMS, "OBJ", "DAMAG");
log_type_and_name(obj);
if (loc != NOTHING)
{
log_text(T(" in "));
log_type_and_name(loc);
}
log_text(T(": "));
log_text(valtype);
log_text(T(" "));
if (is_object)
{
log_type_and_name(val);
}
else
{
log_number(val);
}
log_text(T(" "));
log_text(errtype);
ENDLOG;
}
static void Log_simple_err(dbref obj, dbref loc, const UTF8 *errtype)
{
STARTLOG(LOG_PROBLEMS, "OBJ", "DAMAG");
log_type_and_name(obj);
if (loc != NOTHING)
{
log_text(T(" in "));
log_type_and_name(loc);
}
log_text(T(": "));
log_text(errtype);
ENDLOG;
}
/*
* ---------------------------------------------------------------------------
* * start_home, default_home, can_set_home, new_home, clone_home:
* * Routines for validating and determining homes.
*/
dbref start_home(void)
{
if (mudconf.start_home != NOTHING)
{
return mudconf.start_home;
}
return mudconf.start_room;
}
dbref default_home(void)
{
if (mudconf.default_home != NOTHING)
{
return mudconf.default_home;
}
if (mudconf.start_home != NOTHING)
{
return mudconf.start_home;
}
return mudconf.start_room;
}
bool can_set_home(dbref player, dbref thing, dbref home)
{
if ( !Good_obj(player)
|| !Good_obj(home)
|| thing == home)
{
return false;
}
switch (Typeof(home))
{
case TYPE_PLAYER:
case TYPE_ROOM:
case TYPE_THING:
if (Going(home))
{
return false;
}
if ( Controls(player, home)
|| Abode(home))
{
return true;
}
}
return false;
}
dbref new_home(dbref player)
{
dbref loc = Location(player);
if (can_set_home(Owner(player), player, loc))
{
return loc;
}
loc = Home(Owner(player));
if (can_set_home(Owner(player), player, loc))
{
return loc;
}
return default_home();
}
dbref clone_home(dbref player, dbref thing)
{
dbref loc = Home(thing);
if (can_set_home(Owner(player), player, loc))
{
return loc;
}
return new_home(player);
}
// Manage A_NEWOBJS stored on each player recording the newest object of
// each type created. Data is ROOM, THING, EXIT, PLAYER, and the Latest
//
static void update_newobjects(dbref player, dbref object_num, int object_type)
{
int aowner;
int aflags;
UTF8 *newobject_string = atr_get("update_newobjs.189", player, A_NEWOBJS,
&aowner, &aflags);
// This leaves room for Room, Thing, Exit, and Player.
//
int i;
dbref object_list[4];
if ( nullptr == newobject_string
|| '\0' == newobject_string[0])
{
for (i = 0; i < 4; i++)
{
object_list[i] = -1;
}
}
else
{
string_token st(newobject_string, T(" "));
UTF8* ptr;
for ( ptr = st.parse(), i = 0;
nullptr != ptr && i < 4;
ptr = st.parse(), i++)
{
object_list[i] = mux_atol(ptr);
}
}
free_lbuf(newobject_string);
switch (object_type)
{
case TYPE_ROOM:
object_list[0] = object_num;
break;
case TYPE_THING:
object_list[1] = object_num;
break;
case TYPE_EXIT:
object_list[2] = object_num;
break;
case TYPE_PLAYER:
object_list[3] = object_num;
break;
}
UTF8 tbuf[SBUF_SIZE];
mux_sprintf(tbuf, SBUF_SIZE, T("%d %d %d %d %d"), object_list[0],
object_list[1], object_list[2], object_list[3], object_num);
atr_add_raw(player, A_NEWOBJS, tbuf);
}
// Handle @acreate on master room objects
//
static void ProcessMasterRoomACreate(dbref creator, dbref thing)
{
int nxargs = 2;
const UTF8 *xargs[2];
switch (Typeof(thing))
{
case TYPE_ROOM:
xargs[1] = T("ROOM");
break;
case TYPE_EXIT:
xargs[1] = T("EXIT");
break;
case TYPE_PLAYER:
xargs[1] = T("PLAYER");
break;
case TYPE_THING:
xargs[1] = T("THING");
break;
default:
xargs[1] = T("#-1");
break;
}
dbref master_room_obj = -1;
DOLIST(master_room_obj, Contents(mudconf.master_room))
{
if (thing == master_room_obj)
{
break;
}
if (Controls(master_room_obj, thing))
{
int aowner, aflags;
UTF8* act = atr_pget(master_room_obj, A_ACREATE, &aowner, &aflags);
if ('\0' != act[0])
{
CLinearTimeAbsolute lta;
UTF8 buf[SBUF_SIZE];
mux_sprintf(buf, SBUF_SIZE, T("#%d"), thing);
xargs[0] = buf;
wait_que(master_room_obj, creator, creator,
AttrTrace(aflags, 0), false, lta, NOTHING, 0, act, nxargs,
(const UTF8 **) xargs, mudstate.global_regs);
}
free_lbuf(act);
}
}
}
/*
* ---------------------------------------------------------------------------
* * create_obj: Create an object of the indicated type IF the player can
* * afford it.
*/
dbref create_obj(dbref player, int objtype, const UTF8 *name, int cost)
{
dbref obj, owner;
dbref target_parent = NOTHING;
int quota = 0, value = 0;
size_t nValidName;
FLAGSET f;
UTF8 *buff;
const UTF8 *pValidName;
const UTF8 *tname;
bool okname = false, self_owned = false, require_inherit = false;
bool bRequiredAttrWritesOk = true;
switch (objtype)
{
case TYPE_ROOM:
cost = mudconf.digcost;
quota = mudconf.room_quota;
f = mudconf.room_flags;
target_parent = mudconf.room_parent;
pValidName = MakeCanonicalObjectName(name, &nValidName, &okname, mudconf.room_name_charset);
tname = T("a room");
break;
case TYPE_THING:
if (cost < mudconf.createmin)
cost = mudconf.createmin;
if (cost > mudconf.createmax)
cost = mudconf.createmax;
quota = mudconf.thing_quota;
f = mudconf.thing_flags;
target_parent = mudconf.thing_parent;
value = OBJECT_ENDOWMENT(cost);
pValidName = MakeCanonicalObjectName(name, &nValidName, &okname, mudconf.thing_name_charset);
tname = T("a thing");
break;
case TYPE_EXIT:
cost = mudconf.opencost;
quota = mudconf.exit_quota;
f = mudconf.exit_flags;
target_parent = mudconf.exit_parent;
pValidName = MakeCanonicalExitName(name, &nValidName, &okname);
tname = T("an exit");
break;
case TYPE_PLAYER:
if (cost)
{
cost = mudconf.robotcost;
quota = mudconf.player_quota;
f = mudconf.robot_flags;
value = 0;
tname = T("a robot");
require_inherit = true;
}
else
{
cost = 0;
quota = 0;
f = mudconf.player_flags;
value = mudconf.paystart;
quota = mudconf.start_quota;
self_owned = true;
tname = T("a player");
}
target_parent = mudconf.player_parent;
buff = munge_space(name);
pValidName = name;
if (!badname_check(buff))
{
notify(player, T("That name is not allowed."));
free_lbuf(buff);
return NOTHING;
}
if (*buff)
{
okname = ValidatePlayerName(buff);
if (!okname)
{
notify(player, T("That\xE2\x80\x99s a silly name for a player."));
free_lbuf(buff);
return NOTHING;
}
}
if (okname)
{
bool bAlias = false;
okname = (lookup_player_name(buff, bAlias) == NOTHING);
if (!okname)
{
notify(player, tprintf(T("The name %s is already taken."), name));
free_lbuf(buff);
return NOTHING;
}
}
free_lbuf(buff);
break;
default:
LOG_SIMPLE(LOG_BUGS, "BUG", "OTYPE", tprintf(T("Bad object type in create_obj: %d."), objtype));
return NOTHING;
}
if (!okname)
{
notify(player, tprintf(T("That\xE2\x80\x99s a silly name for %s!"), tname));
return NOTHING;
}
if (!self_owned)
{
if (!Good_obj(player))
{
return NOTHING;
}
owner = Owner(player);
if (!Good_obj(owner))
{
return NOTHING;
}
}
else
{
owner = NOTHING;
}
if (require_inherit)
{
if (!Inherits(player))
{
notify(player, NOPERM_MESSAGE);
return NOTHING;
}
}
// Make sure the creator can pay for the object.
//
if ((player != NOTHING) && !canpayfees(player, player, cost, quota))
{
return NOTHING;
}
// Get the first object from the freelist. If the object is not clean,
// discard the remainder of the freelist and go get a completely new object.
//
obj = NOTHING;
if (mudstate.freelist != NOTHING)
{
obj = mudstate.freelist;
if (IS_CLEAN(obj))
{
mudstate.freelist = Link(obj);
}
else
{
LOG_SIMPLE(LOG_PROBLEMS, "FRL", "DAMAG", tprintf(T("Freelist damaged, bad object #%d."), obj));
obj = NOTHING;
mudstate.freelist = NOTHING;
}
}
if (obj == NOTHING)
{
obj = mudstate.db_top;
db_grow(mudstate.db_top + 1);
}
atr_free(obj); // just in case.
// Set things up according to the object type.
//
s_Location(obj, NOTHING);
s_Contents(obj, NOTHING);
s_Exits(obj, NOTHING);
s_Next(obj, NOTHING);
s_Link(obj, NOTHING);
if (Good_obj(target_parent))
{
s_Parent(obj, target_parent);
}
else
{
s_Parent(obj, NOTHING);
}
if (mudconf.autozone && player != NOTHING)
{
s_Zone(obj, Zone(player));
}
else
{
s_Zone(obj, NOTHING);
}
f.word[FLAG_WORD1] |= objtype;
db[obj].fs = f;
s_Owner(obj, (self_owned ? obj : owner));
s_Pennies(obj, value);
if (Pennies(obj) != value)
{
bRequiredAttrWritesOk = false;
}
Unmark(obj);
buff = munge_space(pValidName);
s_Name(obj, buff);
if (nullptr == db[obj].name)
{
bRequiredAttrWritesOk = false;
}
free_lbuf(buff); buff = nullptr;
db[obj].cpu_time_used.Set100ns(0);
db[obj].tThrottleExpired.Set100ns(0);
s_ThAttrib(obj, 0);
s_ThMail(obj, 0);
s_ThRefs(obj, 0);
CLinearTimeAbsolute ltaNow;
ltaNow.GetLocal();
const UTF8 *datestr = ltaNow.ReturnDateString(7);
bRequiredAttrWritesOk = bRequiredAttrWritesOk && atr_add_raw(obj, A_CREATED, datestr);
bRequiredAttrWritesOk = bRequiredAttrWritesOk && atr_add_raw(obj, A_MODIFIED, datestr);
if (objtype == TYPE_PLAYER)
{
bRequiredAttrWritesOk = bRequiredAttrWritesOk && atr_add_raw(obj, A_LAST, datestr);
UTF8 *buff2 = alloc_sbuf("create_obj.quota");
mux_ltoa(quota, buff2);
bRequiredAttrWritesOk = bRequiredAttrWritesOk && atr_add_raw(obj, A_QUOTA, buff2);
bRequiredAttrWritesOk = bRequiredAttrWritesOk && atr_add_raw(obj, A_RQUOTA, buff2);
free_sbuf(buff2);
s_Zone(obj, NOTHING);
}
if (!bRequiredAttrWritesOk)
{
STARTLOG(LOG_ALWAYS, "DB", "OBJSYNC");
log_text(T("create_obj required attribute persistence failed; refusing object creation."));
ENDLOG;
destroy_obj(obj);
if (!IS_CLEAN(obj))
{
force_reclaim_failed_create_slot(obj);
}
else
{
s_Link(obj, mudstate.freelist);
mudstate.freelist = obj;
}
reconcile_failed_create_backend(obj);
return NOTHING;
}
CSQLiteDB::ObjectRecord rec;
rec.dbref_val = obj;
rec.location = db[obj].location;
rec.contents = db[obj].contents;
rec.exits = db[obj].exits;
rec.next = db[obj].next;
rec.link = db[obj].link;
rec.owner = db[obj].owner;
rec.parent = db[obj].parent;
rec.zone = db[obj].zone;
rec.pennies = Pennies(obj);
rec.flags1 = db[obj].fs.word[FLAG_WORD1];
rec.flags2 = db[obj].fs.word[FLAG_WORD2];
rec.flags3 = db[obj].fs.word[FLAG_WORD3];
rec.powers1 = db[obj].powers;
rec.powers2 = db[obj].powers2;
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
bool bPersisted = false;
if (!sqldb.Begin())
{
STARTLOG(LOG_PROBLEMS, "DB", "OBJSYNC");
log_text(T("Begin transaction failed in create_obj."));
ENDLOG;
}
else if (!sqldb.InsertObject(rec))
{
STARTLOG(LOG_PROBLEMS, "DB", "OBJSYNC");
log_text(T("InsertObject failed in create_obj."));
ENDLOG;
sqldb.Rollback();
}
else if (!sqldb.PutMeta("db_top", mudstate.db_top))
{
STARTLOG(LOG_PROBLEMS, "DB", "OBJSYNC");
log_text(T("PutMeta(db_top) failed in create_obj."));
ENDLOG;
sqldb.Rollback();
}
else if (!sqldb.Commit())
{
STARTLOG(LOG_PROBLEMS, "DB", "OBJSYNC");
log_text(T("Commit failed in create_obj."));
ENDLOG;
sqldb.Rollback();
}
else
{
bPersisted = true;
}
if (!bPersisted)
{
STARTLOG(LOG_ALWAYS, "DB", "OBJSYNC");
log_text(T("create_obj persistence failed; attempting SQLite metadata resync."));
ENDLOG;
if (!sqlite_sync_runtime())
{
STARTLOG(LOG_ALWAYS, "DB", "OBJSYNC");
log_text(T("create_obj recovery sqlite_sync_runtime() failed."));
ENDLOG;
// Fail closed: don't hand out an object we couldn't make durable.
//
destroy_obj(obj);
if (!IS_CLEAN(obj))
{
force_reclaim_failed_create_slot(obj);
}
else
{
s_Link(obj, mudstate.freelist);
mudstate.freelist = obj;
}
reconcile_failed_create_backend(obj);
return NOTHING;
}
}
// Side effects are only applied after object durability is guaranteed.
//
if (objtype == TYPE_PLAYER)
{
add_player_name(obj, Name(obj), false);
}
if (Good_obj(player))
{
update_newobjects(player, obj, objtype);
ProcessMasterRoomACreate(player, obj);
}
return obj;
}
/*
* ---------------------------------------------------------------------------
* * destroy_obj: Destroy an object. Assumes it has already been removed from
* * all lists and has no contents or exits.
*/
static void destroy_bad_obj(dbref obj)
{
if (!mudstate.bStandAlone)
{
halt_que(NOTHING, obj);
nfy_que(obj, 0, NFY_DRAIN, 0);
fwdlist_clr(obj);
ReleaseAllResources(obj);
}
atr_free(obj);
s_Name(obj, nullptr);
s_Flags(obj, FLAG_WORD1, (TYPE_GARBAGE | GOING));
s_Flags(obj, FLAG_WORD2, 0);
s_Flags(obj, FLAG_WORD3, 0);
s_Powers(obj, 0);
s_Powers2(obj, 0);
s_Location(obj, NOTHING);
s_Contents(obj, NOTHING);
s_Exits(obj, NOTHING);
s_Next(obj, NOTHING);
s_Link(obj, NOTHING);
s_Owner(obj, GOD);
s_Pennies(obj, 0);
s_Parent(obj, NOTHING);
s_Zone(obj, NOTHING);
}
void destroy_obj(dbref obj)
{
if (!Good_obj(obj))
{
if ( (obj >= 0)
&& (obj < mudstate.db_top))
{
destroy_bad_obj(obj);
}
return;
}
// Validate the owner.
//
dbref owner = Owner(obj);
bool good_owner = Good_owner(owner);
// Halt any pending commands (waiting or semaphore).
//
if (!mudstate.bStandAlone)
{
if ( halt_que(NOTHING, obj) > 0
&& good_owner
&& !Quiet(obj)
&& !Quiet(owner))
{
notify(owner, T("Halted."));
}
nfy_que(obj, 0, NFY_DRAIN, 0);
// Remove forwardlists.
//
fwdlist_clr(obj);
}
// Compensate the owner for the object.
//
if ( good_owner
&& owner != obj)
{
int val = 0;
int quota = 0;
switch (Typeof(obj))
{
case TYPE_ROOM:
val = mudconf.digcost;
quota = mudconf.room_quota;
break;
case TYPE_THING:
val = OBJECT_DEPOSIT(Pennies(obj));
quota = mudconf.thing_quota;
break;
case TYPE_EXIT:
if (Location(obj) == NOTHING)
{
val = mudconf.opencost;
}
else
{
val = mudconf.opencost + mudconf.linkcost;
}
quota = mudconf.exit_quota;
break;
case TYPE_PLAYER:
if (Robot(obj))
{
val = mudconf.robotcost;
}
else
{
val = 0;
}
quota = mudconf.player_quota;
break;
}
if (val)
{
giveto(owner, val);
if ( !Quiet(owner)
&& !Quiet(obj))
{
notify(owner,
tprintf(T("You get back your %d %s deposit for %s(#%d)."),
val, mudconf.one_coin, Moniker(obj), obj));
}
}
if ( mudconf.quotas
&& quota)
{
add_quota(owner, quota);
}
}
if (!mudstate.bStandAlone)
{
ReleaseAllResources(obj);
}
atr_free(obj);
s_Name(obj, nullptr);
s_Flags(obj, FLAG_WORD1, (TYPE_GARBAGE | GOING));
s_Flags(obj, FLAG_WORD2, 0);
s_Flags(obj, FLAG_WORD3, 0);
s_Powers(obj, 0);
s_Powers2(obj, 0);
s_Location(obj, NOTHING);
s_Contents(obj, NOTHING);
s_Exits(obj, NOTHING);
s_Next(obj, NOTHING);
s_Link(obj, NOTHING);
s_Owner(obj, GOD);
s_Pennies(obj, 0);
s_Parent(obj, NOTHING);
s_Zone(obj, NOTHING);
local_data_free(obj);
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
while (nullptr != p)
{
p->pSink->data_free(obj);
p = p->pNext;
}
}
/*
* ---------------------------------------------------------------------------
* * make_freelist: Build a freelist
*/
static void make_freelist(void)
{
dbref i;
mudstate.freelist = NOTHING;
DO_WHOLE_DB_BACKWARDS(i)
{
if (IS_CLEAN(i))
{
s_Link(i, mudstate.freelist);
mudstate.freelist = i;
}
}
}
/*
* ---------------------------------------------------------------------------
* * divest_object: Get rid of KEY contents of object.
*/
void divest_object(dbref thing)
{
dbref curr, temp;
SAFE_DOLIST(curr, temp, Contents(thing))
{
if ( !Controls(thing, curr)
&& Has_location(curr)
&& Key(curr))
{
if ( !Good_obj(Home(curr))
|| Going(Home(curr)))
{
s_Home(curr, new_home(curr));
}
move_via_generic(curr, HOME, NOTHING, 0);
}
}
}
/*
* ---------------------------------------------------------------------------
* * empty_obj, purge_going: Get rid of GOING objects in the db.
*/
void empty_obj(dbref obj)
{
dbref targ, next;
// Send the contents home
//
SAFE_DOLIST(targ, next, Contents(obj))
{
if (!Has_location(targ))
{
Log_simple_err(targ, obj,
T("Funny object type in contents list of GOING location. Flush terminated."));
break;
}
else if (Location(targ) != obj)
{
Log_header_err(targ, obj, Location(targ), true,
T("Location"),
T("indicates object really in another location during cleanup of GOING location. Flush terminated."));
break;
}
else
{
s_Location(targ, NOTHING);
s_Next(targ, NOTHING);
if ( !Good_obj(Home(targ))
|| Going(Home(targ))
|| Home(targ) == obj)
{
s_Home(targ, new_home(targ));
}
move_via_generic(targ, HOME, NOTHING, 0);
divest_object(targ);
}
}
// Destroy the exits.
//
SAFE_DOLIST(targ, next, Exits(obj))
{
if (!isExit(targ) && !isGarbage(targ))
{
Log_simple_err(targ, obj, T("Funny object type in exit list of GOING location. Flush terminated."));
break;
}
else if (Exits(targ) != obj)
{
Log_header_err(targ, obj, Exits(targ), true, T("Location"),
T("indicates exit really in another location during cleanup of GOING location. Flush terminated."));
break;
}
else
{
destroy_obj(targ);
}
}
}
/*
* ---------------------------------------------------------------------------
* * destroy_exit, destroy_thing, destroy_player
*/
void destroy_exit(dbref exit)
{
dbref loc = Exits(exit);
s_Exits(loc, remove_first(Exits(loc), exit));
destroy_obj(exit);
}
void destroy_thing(dbref thing)
{
move_via_generic(thing, NOTHING, Owner(thing), 0);
empty_obj(thing);
destroy_obj(thing);
}
void destroy_player(dbref player, dbref victim)
{
// Bye bye...
//
boot_off(victim, T("You have been destroyed!"));
halt_que(victim, NOTHING);
int count = chown_all(victim, player, player, CHOWN_NOZONE);
// Remove the name from the name hash table.
//
delete_player_name(victim, Name(victim), false);
dbref aowner;
int aflags;
UTF8 *buf = atr_pget(victim, A_ALIAS, &aowner, &aflags);
delete_player_name(victim, buf, true);
free_lbuf(buf);
// Purge mailbox and orphan sent mail before the dbref is recycled.
//
mail_destroy_player(victim);
move_via_generic(victim, NOTHING, player, 0);
destroy_obj(victim);
notify_quiet(player, tprintf(T("(%d objects @chowned to you)"), count));
}
static void purge_going(void)
{
dbref i;
DO_WHOLE_DB(i)
{
if (!Going(i))
{
continue;
}
if (Indestructible(i))
{
s_Flags(i, FLAG_WORD1, db[i].fs.word[FLAG_WORD1] & ~GOING);
continue;
}
const UTF8 *p;
switch (Typeof(i))
{
case TYPE_PLAYER:
p = atr_get_raw(i, A_DESTROYER);
if (!p)
{
STARTLOG(LOG_PROBLEMS, "OBJ", "DAMAG");
log_type_and_name(i);
dbref loc = Location(i);
if (loc != NOTHING)
{
log_text(T(" in "));
log_type_and_name(loc);
}
log_text(T("GOING object doesn\xE2\x80\x99t remember its destroyer. GOING reset."));
ENDLOG;
s_Flags(i, FLAG_WORD1, db[i].fs.word[FLAG_WORD1] & ~GOING);
}
else
{
dbref player = static_cast<dbref>(mux_atol(p));
destroy_player(player, i);
}
break;
case TYPE_ROOM:
// Room scheduled for destruction... do it.
//
empty_obj(i);
destroy_obj(i);
break;
case TYPE_THING:
destroy_thing(i);
break;
case TYPE_EXIT:
destroy_exit(i);
break;
case TYPE_GARBAGE:
break;
default:
// Something else... How did this happen?
//
Log_simple_err(i, NOTHING,
T("GOING object with unexpected type. Destroyed."));
destroy_obj(i);
}
}
}
// ---------------------------------------------------------------------------
// check_dead_refs: Look for references to GOING or illegal objects.
//
static void check_pennies(dbref thing, int limit, const UTF8 *qual)
{
if (Going(thing))
{
return;
}
int j = Pennies(thing);
if (j)
{
if (isRoom(thing) || isExit(thing))
{
Log_header_err(thing, NOTHING, j, false, qual, T("is strange. Reset."));
s_Pennies(thing, 0);
}
else if (j < 0)
{
Log_header_err(thing, NOTHING, j, false, qual, T("is negative."));
}
else if (limit < j)
{
Log_header_err(thing, NOTHING, j, false, qual, T("is excessive."));
}
}
else
{
if (isPlayer(thing) || isThing(thing))
{
Log_header_err(thing, NOTHING, j, false, qual, T("is zero."));
}
}
}
static void check_dead_refs(void)
{
dbref targ, owner, i, j;
int aflags;
UTF8 *str;
FWDLIST *fp;
bool dirty;
DO_WHOLE_DB(i)
{
// Check the owner.
//
owner = Owner(i);
if (!Good_obj(owner))
{
if (isPlayer(i))
{
Log_header_err(i, NOTHING, owner, true, T("Owner"),
T("is invalid. Set to player."));
owner = i;
}
else
{
Log_header_err(i, NOTHING, owner, true, T("Owner"),
T("is invalid. Set to GOD."));
owner = GOD;
}
s_Owner(i, owner);
if (!mudstate.bStandAlone)
{
halt_que(NOTHING, i);
}
s_Halted(i);
}
else if (check_type & DBCK_FULL)
{
if (Going(owner))
{
if (isPlayer(i))
{
Log_header_err(i, NOTHING, owner, true,
T("Owner"), T("is set GOING. Set to player."));
owner = i;
}
else
{
Log_header_err(i, NOTHING, owner, true,
T("Owner"), T("is set GOING. Set to GOD."));
owner = GOD;
}
s_Owner(i, owner);
if (!mudstate.bStandAlone)
{
halt_que(NOTHING, i);
}
s_Halted(i);
}
else if (!OwnsOthers(owner))
{
if (isPlayer(i))
{
Log_header_err(i, NOTHING, owner, true,
T("Owner"), T("is not a valid owner type. Set to player."));
owner = i;
}
else
{
Log_header_err(i, NOTHING, owner, true,
T("Owner"), T("is not a valid owner type. Set to GOD."));
owner = GOD;
}
s_Owner(i, owner);
}
}
// Check the parent
//
targ = Parent(i);
if (Good_obj(targ))
{
if (Going(targ))
{
s_Parent(i, NOTHING);
if (!mudstate.bStandAlone)
{
if ( !Quiet(i)
&& !Quiet(owner))
{
notify(owner, tprintf(T("Parent cleared on %s(#%d)"),
Moniker(i), i));
}
}
else
{
Log_header_err(i, Location(i), targ, true, T("Parent"),
T("is invalid. Cleared."));
}
}
}
else if (targ != NOTHING)
{
Log_header_err(i, Location(i), targ, true,
T("Parent"), T("is invalid. Cleared."));
s_Parent(i, NOTHING);
}
// Check the zone.
//
targ = Zone(i);
if (Good_obj(targ))
{
if (Going(targ))
{
s_Zone(i, NOTHING);
if (!mudstate.bStandAlone)
{
owner = Owner(i);
if ( !Quiet(i)
&& !Quiet(owner))
{
notify(owner, tprintf(T("Zone cleared on %s(#%d)"),
Moniker(i), i));
}
}
else
{
Log_header_err(i, Location(i), targ, true, T("Zone"),
T("is invalid. Cleared."));
}
}
}
else if (targ != NOTHING)
{
Log_header_err(i, Location(i), targ, true, T("Zone"),
T("is invalid. Cleared."));
s_Zone(i, NOTHING);
}
// Check forwardlist
//
fp = fwdlist_get(i);
dirty = false;
if (fp)
{
for (j = 0; j < fp->count; j++)
{
targ = fp->data[j];
if ( Good_obj(targ)
&& Going(targ))
{
fp->data[j] = NOTHING;
dirty = true;
}
else if ( !Good_obj(targ)
&& targ != NOTHING)
{
fp->data[j] = NOTHING;
dirty = true;
}
}
}
if (dirty)
{
str = alloc_lbuf("purge_going");
(void)fwdlist_rewrite(fp, str);
atr_get_info(i, A_FORWARDLIST, &owner, &aflags);
atr_add(i, A_FORWARDLIST, str, owner, aflags);
free_lbuf(str);
}
if (check_type & DBCK_FULL)
{
// Check for wizards
//
if (RealWizard(i))
{
if (isPlayer(i))
{
Log_simple_err(i, NOTHING, T("Player is a WIZARD."));
}
if (!Wizard(Owner(i)))
{
Log_header_err(i, NOTHING, Owner(i), true,
T("Owner"), T("of a WIZARD object is not a wizard"));
}
}
}
switch (Typeof(i))
{
case TYPE_PLAYER:
// Check home.
//
targ = Home(i);
if ( !Good_obj(targ)
|| !Has_contents(targ))
{
Log_simple_err(i, Location(i), T("Bad home. Reset."));
s_Home(i, default_home());
}
// Check the location.
//
targ = Location(i);
if ( !Good_obj(targ)
|| !Has_contents(targ))
{
Log_pointer_err(NOTHING, i, NOTHING, targ, T("Location"),
T("is invalid. Moved to home."));
move_object(i, Home(i));
}
// Check for self-referential Next().
//
if (Next(i) == i)
{
Log_simple_err(i, NOTHING,
T("Next points to self. Next cleared."));
s_Next(i, NOTHING);
}
if (check_type & DBCK_FULL)
{
// Check wealth.
//
targ = mudconf.paylimit;
check_pennies(i, targ, T("Wealth"));
}
break;
case TYPE_THING:
// Check home.
//
targ = Home(i);
if ( !Good_obj(targ)
|| !Has_contents(targ))
{
if (!mudstate.bStandAlone)
{
if ( !Quiet(i)
&& !Quiet(owner))
{
notify(owner, tprintf(T("Home reset on %s(#%d)"),
Moniker(i), i));
}
else
{
Log_header_err(i, Location(i), targ, true, T("Home"),
T("is invalid. Cleared."));
}
}
s_Home(i, new_home(i));
}
// Check the location.
//
targ = Location(i);
if ( !Good_obj(targ)
|| !Has_contents(targ))
{
Log_pointer_err(NOTHING, i, NOTHING, targ, T("Location"),
T("is invalid. Moved to home."));
move_object(i, HOME);
}
// Check for self-referential Next().
//
if (Next(i) == i)
{
Log_simple_err(i, NOTHING,
T("Next points to self. Next cleared."));
s_Next(i, NOTHING);
}
if (check_type & DBCK_FULL)
{
// Check value.
//
targ = OBJECT_ENDOWMENT(mudconf.createmax);
check_pennies(i, targ, T("Value"));
}
break;
case TYPE_ROOM:
// Check the dropto.
//
targ = Dropto(i);
if (Good_obj(targ))
{
if (Going(targ))
{
s_Dropto(i, NOTHING);
if (!mudstate.bStandAlone)
{
if ( !Quiet(i)
&& !Quiet(owner))
{
notify(owner, tprintf(T("Dropto removed from %s(#%d)"),
Moniker(i), i));
}
}
else
{
Log_header_err(i, NOTHING, targ, true, T("Dropto"),
T("is invalid. Removed."));
}
}
}
else if ( targ != NOTHING
&& targ != HOME)
{
Log_header_err(i, NOTHING, targ, true, T("Dropto"),
T("is invalid. Cleared."));
s_Dropto(i, NOTHING);
}
if (check_type & DBCK_FULL)
{
// NEXT should be null.
//
if (Next(i) != NOTHING)
{
Log_header_err(i, NOTHING, Next(i), true, T("Next pointer"),
T("should be NOTHING. Reset."));
s_Next(i, NOTHING);
}
// LINK should be null.
//
if (Link(i) != NOTHING)
{
Log_header_err(i, NOTHING, Link(i), true, T("Link pointer "),
T("should be NOTHING. Reset."));
s_Link(i, NOTHING);
}
// Check value.
//
check_pennies(i, 1, T("Value"));
}
break;
case TYPE_EXIT:
// If it points to something GOING, set it going.
//
targ = Location(i);
if (Good_obj(targ))
{
if (Going(targ))
{
s_Going(i);
}
}
else if (targ == HOME)
{
// null case, HOME is always valid.
//
}
else if (targ != NOTHING)
{
Log_header_err(i, Exits(i), targ, true, T("Destination"),
T("is invalid. Exit destroyed."));
s_Going(i);
}
else
{
if (!Has_contents(targ))
{
Log_header_err(i, Exits(i), targ, true, T("Destination"),
T("is not a valid type. Exit destroyed."));
s_Going(i);
}
}
// Check for self-referential Next().
//
if (Next(i) == i)
{
Log_simple_err(i, NOTHING,
T("Next points to self. Next cleared."));
s_Next(i, NOTHING);
}
if (check_type & DBCK_FULL)
{
// CONTENTS should be null.
//
if (Contents(i) != NOTHING)
{
Log_header_err(i, Exits(i), Contents(i), true, T("Contents"),
T("should be NOTHING. Reset."));
s_Contents(i, NOTHING);
}
// LINK should be null.
//
if (Link(i) != NOTHING)
{
Log_header_err(i, Exits(i), Link(i), true, T("Link"),
T("should be NOTHING. Reset."));
s_Link(i, NOTHING);
}
// Check value.
//
check_pennies(i, 1, T("Value"));
}
break;
case TYPE_GARBAGE:
break;
default:
// Funny object type, destroy it.
//
Log_simple_err(i, NOTHING, T("Funny object type. Destroyed."));
destroy_obj(i);
}
}
}
/*
* ---------------------------------------------------------------------------
* * check_loc_exits, check_exit_chains: Validate the exits chains
* * of objects and attempt to correct problems. The following errors are
* * found and corrected:
* * Location not in database - skip it.
* * Location GOING - skip it.
* * Location not a PLAYER, ROOM, or THING - skip it.
* * Location already visited - skip it.
* * Exit/next pointer not in database - nullptr it.
* * Member is not an EXIT - terminate chain.
* * Member is GOING - destroy exit.
* * Member already checked (is in another list) - terminate chain.
* * Member in another chain (recursive check) - terminate chain.
* * Location of member is not specified location - reset it.
*/
static void check_loc_exits(dbref loc)
{
if (!Good_obj(loc))
{
return;
}
// Only check players, rooms, and things that aren't GOING.
//
if ( isExit(loc)
|| Going(loc))
{
return;
}
// If marked, we've checked here already.
//
if (Marked(loc))
{
return;
}
Mark(loc);
// Check all the exits.
//
dbref temp, exitloc, dest;
dbref back = NOTHING;
dbref exit = Exits(loc);
while (exit != NOTHING)
{
exitloc = NOTHING;
dest = NOTHING;
if (Good_obj(exit))
{
exitloc = Exits(exit);
dest = Location(exit);
}
if (!Good_obj(exit))
{
// A bad pointer - terminate chain.
//
Log_pointer_err(back, loc, NOTHING, exit, T("Exit list"),
T("is invalid. List nulled."));
if (back != NOTHING)
{
s_Next(back, NOTHING);
}
else
{
s_Exits(loc, NOTHING);
}
exit = NOTHING;
}
else if (!isExit(exit))
{
// Not an exit - terminate chain.
//
Log_pointer_err(back, loc, NOTHING, exit, T("Exitlist member"),
T("is not an exit. List terminated."));
if (back != NOTHING)
{
s_Next(back, NOTHING);
}
else
{
s_Exits(loc, NOTHING);
}
exit = NOTHING;
}
else if (Going(exit))
{
// Going - silently filter out.
//
temp = Next(exit);
if (back != NOTHING)
{
s_Next(back, temp);
}
else
{
s_Exits(loc, temp);
}
destroy_obj(exit);
exit = temp;
continue;
}
else if (Marked(exit))
{
// Already in another list - terminate chain.
//
Log_pointer_err(back, loc, NOTHING, exit, T("Exitlist member"),
T("is in another exitlist. Cleared."));
if (back != NOTHING)
{
s_Next(back, NOTHING);
}
else
{
s_Exits(loc, NOTHING);
}
exit = NOTHING;
}
else if ( !Good_obj(dest)
&& dest != HOME
&& dest != NOTHING)
{
// Destination is not in the db. Null it.
//
Log_pointer_err(back, loc, NOTHING, exit, T("Destination"),
T("is invalid. Cleared."));
s_Location(exit, NOTHING);
}
else if (exitloc != loc)
{
// Exit thinks it's in another place. Check the exitlist there
// and see if it contains this exit. If it does, then our exitlist
// somehow pointed into the middle of their exitlist. If not,
// assume we own the exit.'
//
check_loc_exits(exitloc);
if (Marked(exit))
{
// It's in the other list, give it up.
//
Log_pointer_err(back, loc, NOTHING, exit, T(""),
T("is in another exitlist. List terminated."));
if (back != NOTHING)
{
s_Next(back, NOTHING);
}
else
{
s_Exits(loc, NOTHING);
}
exit = NOTHING;
}
else
{
// Not in the other list, assume in ours.
//
Log_header_err(exit, loc, exitloc, true,
T("Not on chain for location"), T("Reset."));
s_Exits(exit, loc);
}
}
if (exit != NOTHING)
{
// All OK (or all was made OK).
//
if (check_type & DBCK_FULL)
{
// Make sure exit owner owns at least one of the source or
// destination. Just warn if he doesn't.
//
temp = Owner(exit);
if ( temp != Owner(loc)
&& !( Good_dbref(dest)
&& temp == Owner(dest)))
{
Log_header_err(exit, loc, temp, true, T("Owner"),
T("does not own either the source or destination."));
}
}
Mark(exit);
back = exit;
exit = Next(exit);
}
}
return;
}
static void check_exit_chains(void)
{
dbref i;
Unmark_all(i);
DO_WHOLE_DB(i)
{
check_loc_exits(i);
}
DO_WHOLE_DB(i)
{
if ( isExit(i)
&& !Marked(i))
{
Log_simple_err(i, NOTHING, T("Disconnected exit. Destroyed."));
destroy_obj(i);
}
}
}
/*
* ---------------------------------------------------------------------------
* * check_misplaced_obj, check_loc_contents, check_contents_chains: Validate
* * the contents chains of objects and attempt to correct problems. The
* * following errors are found and corrected:
* * Location not in database - skip it.
* * Location GOING - skip it.
* * Location not a PLAYER, ROOM, or THING - skip it.
* * Location already visited - skip it.
* * Contents/next pointer not in database - nullptr it.
* * Member is not an PLAYER or THING - terminate chain.
* * Member is GOING - destroy exit.
* * Member already checked (is in another list) - terminate chain.
* * Member in another chain (recursive check) - terminate chain.
* * Location of member is not specified location - reset it.
*/
static void check_loc_contents(dbref);
static void check_misplaced_obj(dbref *obj, dbref back, dbref loc)
{
// Object thinks it's in another place. Check the contents list there
// and see if it contains this object. If it does, then our contents
// list somehow pointed into the middle of their contents list and we
// should truncate our list. If not, assume we own the object.
//
if (!Good_obj(*obj))
{
return;
}
loc = Location(*obj);
Unmark(*obj);
if (Good_obj(loc))
{
check_loc_contents(loc);
}
if (Marked(*obj))
{
// It's in the other list, give it up.
//
Log_pointer_err(back, loc, NOTHING, *obj, T(""),
T("is in another contents list. Cleared."));
if (back != NOTHING)
{
s_Next(back, NOTHING);
}
else
{
s_Contents(loc, NOTHING);
}
*obj = NOTHING;
}
else
{
// Not in the other list, assume in ours.
//
Log_header_err(*obj, loc, Contents(*obj), true, T("Location"),
T("is invalid. Reset."));
s_Contents(*obj, loc);
}
return;
}
static void check_loc_contents(dbref loc)
{
if (!Good_obj(loc))
{
return;
}
// Only check players, rooms, and things that aren't GOING.
//
if ( isExit(loc)
|| Going(loc))
{
return;
}
dbref back = NOTHING;
dbref obj = Contents(loc);
while (obj != NOTHING)
{
if (!Good_obj(obj))
{
// A bad pointer - terminate chain.
//
Log_pointer_err(back, loc, NOTHING, obj, T("Contents list"),
T("is invalid. Cleared."));
if (back != NOTHING)
{
s_Next(back, NOTHING);
}
else
{
s_Contents(loc, NOTHING);
}
obj = NOTHING;
}
else if (!Has_location(obj))
{
// Not a player or thing - terminate chain.
//
Log_pointer_err(back, loc, NOTHING, obj, T(""),
T("is not a player or thing. Cleared."));
if (back != NOTHING)
{
s_Next(back, NOTHING);
}
else
{
s_Contents(loc, NOTHING);
}
obj = NOTHING;
}
else if (Marked(obj))
{
// Already visited - either truncate or ignore.
//
if (Location(obj) != loc)
{
// Location wrong - either truncate or fix.
//
check_misplaced_obj(&obj, back, loc);
}
else
{
// Location right - recursive contents.
//
}
}
else if (Location(obj) != loc)
{
// Location wrong - either truncate or fix.
//
check_misplaced_obj(&obj, back, loc);
}
if (obj != NOTHING)
{
// All OK (or all was made OK).
//
if (check_type & DBCK_FULL)
{
// Check for wizard command-handlers inside non-wizard. Just
// warn if we find one.
//
if ( RealWizard(obj)
&& !Wizard(loc))
{
if (Commer(obj))
{
Log_simple_err(obj, loc,
T("Wizard command handling object inside non-wizard."));
}
}
// Check for non-wizard objects inside wizard objects.
//
if ( RealWizard(loc)
&& !Wizard(obj)
&& !Wizard(Owner(obj)))
{
Log_simple_err(obj, loc, T("Non-wizard object inside wizard."));
}
}
Mark(obj);
back = obj;
obj = Next(obj);
}
}
}
static void check_contents_chains(void)
{
dbref i;
Unmark_all(i);
DO_WHOLE_DB(i)
{
check_loc_contents(i);
}
DO_WHOLE_DB(i)
{
if ( !Going(i)
&& !Marked(i)
&& Has_location(i))
{
Log_simple_err(i, Location(i), T("Orphaned object, moved home."));
s_Location(i, NOTHING);
s_Next(i, NOTHING);
if ( !Good_obj(Home(i))
|| Going(Home(i)))
{
s_Home(i, new_home(i));
}
move_via_generic(i, HOME, NOTHING, 0);
}
}
}
/*
* ---------------------------------------------------------------------------
* * mark_place, check_floating: Look for floating rooms not set FLOATING.
*/
static void mark_place(dbref loc)
{
// If already marked, exit. Otherwise set marked.
//
if ( !Good_obj(loc)
|| Marked(loc))
{
return;
}
Mark(loc);
// Visit all places you can get to via exits from here.
//
dbref exit;
for (exit = Exits(loc); exit != NOTHING; exit = Next(exit))
{
if (Good_obj(Location(exit)))
{
mark_place(Location(exit));
}
}
}
static void check_floating(void)
{
dbref i;
// Mark everyplace you can get to via exits from the starting room.
//
Unmark_all(i);
mark_place(mudconf.start_room);
// Mark every room you can get to from a floating room.
//
DO_WHOLE_DB(i)
{
if ( isRoom(i)
&& Floating(i)
&& !Going(i))
{
mark_place(i);
}
}
// Look for unvisited rooms.
//
DO_WHOLE_DB(i)
{
if ( isRoom(i)
&& !Going(i)
&& !Marked(i))
{
if (!mudstate.bStandAlone)
{
dbref owner = Owner(i);
if (Good_owner(owner))
{
notify(owner, tprintf(T("You own a floating room: %s(#%d)"),
Moniker(i), i));
}
}
else
{
Log_simple_err(i, NOTHING, T("Disconnected room."));
}
}
}
}
/*
* ---------------------------------------------------------------------------
* * do_dbck: Perform a database consistency check and clean up damage.
*/
void do_dbck(dbref executor, dbref caller, dbref enactor, int eval, int key)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
check_type = key;
check_dead_refs();
check_exit_chains();
check_contents_chains();
check_floating();
if ( !mudstate.bStandAlone
&& executor != NOTHING)
{
Guest.CleanUp();
}
purge_going();
make_freelist();
scheduler.Shrink();
mux_ModuleMaintenance();
#if defined(STUB_SLAVE)
{
mux_ISlaveControl *pISlaveControl = nullptr;
MUX_RESULT mr = mux_CreateInstance(CID_StubSlave, nullptr,
UseSlaveProcess, IID_ISlaveControl,
(void **)&pISlaveControl);
if (MUX_SUCCEEDED(mr) && nullptr != pISlaveControl)
{
pISlaveControl->ModuleMaintenance();
pISlaveControl->Release();
}
}
#endif // STUB_SLAVE
// Allow the local extensions to do data checks.
//
local_dbck();
ServerEventsSinkNode *p = g_pServerEventsSinkListHead;
while (nullptr != p)
{
p->pSink->dbck();
p = p->pNext;
}
if ( !mudstate.bStandAlone
&& executor != NOTHING
&& !Quiet(executor))
{
notify(executor, T("Done."));
}
}