mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
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>
2303 lines
60 KiB
C++
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."));
|
|
}
|
|
}
|