/* object.c - low-level object manipulation routines */
/* $Id: object.c,v 1.33 2000/02/20 00:10:49 cvs Exp $ */
#include "copyright.h"
#include "autoconf.h"
#include "db.h"
#include "mudconf.h"
#include "command.h"
#include "externs.h"
#include "flags.h"
#include "powers.h"
#include "attrs.h"
#include "alloc.h"
#include "match.h"
#define IS_CLEAN(i) (IS(i, TYPE_GARBAGE, GOING) && \
(Location(i) == NOTHING) && \
(Contents(i) == NOTHING) && (Exits(i) == NOTHING) && \
(Next(i) == NOTHING) && (Owner(i) == GOD))
#define ZAP_LOC(i) { s_Location(i, NOTHING); s_Next(i, NOTHING); }
static int check_type;
extern int FDECL(boot_off, (dbref, char *));
#ifdef USE_MAIL
extern void FDECL(do_mail_clear, (dbref, char *));
extern void FDECL(do_mail_purge, (dbref));
#endif
#ifdef USE_COMSYS
extern void FDECL(comsys_chown, (dbref, dbref));
#endif
#ifndef STANDALONE
extern void FDECL(fwdlist_clr, (dbref));
extern void FDECL(stack_clr, (dbref));
extern void FDECL(xvars_clr, (dbref));
extern int FDECL(cron_clr, (dbref, dbref));
extern int FDECL(structure_clr, (dbref));
#ifdef USE_COMSYS
extern int FDECL(channel_clr, (dbref));
#endif
#endif /* ! STANDALONE */
#ifdef STANDALONE
/* Functions needed in standalone mode */
/* move_object: taken from move.c with look and penny check extracted */
void move_object(thing, dest)
dbref thing, dest;
{
dbref src;
/* Remove from the source location */
src = Location(thing);
if (src != NOTHING)
s_Contents(src, remove_first(Contents(src), thing));
/* Special check for HOME */
if (dest == HOME)
dest = Home(thing);
/* Special check for AMBIGUOUS - We should be okay if we make
* this equivalent to NOTHING, I hope...
*/
if (dest == AMBIGUOUS)
dest = NOTHING;
/* Add to destination location */
if (dest != NOTHING) {
s_Contents(dest, insert_first(Contents(dest), thing));
} else {
s_Next(thing, NOTHING);
}
s_Location(thing, dest);
}
#define move_via_generic(obj,where,extra,hush) move_object(obj,where)
#endif
/* ---------------------------------------------------------------------------
* Log_pointer_err, Log_header_err, Log_simple_damage: Write errors to the
* log file.
*/
static void Log_pointer_err(prior, obj, loc, ref, reftype, errtype)
dbref prior, obj, loc, ref;
const char *reftype, *errtype;
{
STARTLOG(LOG_PROBLEMS, "OBJ", "DAMAG")
log_type_and_name(obj);
if (loc != NOTHING) {
log_text((char *)" in ");
log_type_and_name(loc);
}
log_text((char *)": ");
if (prior == NOTHING) {
log_text((char *)reftype);
} else {
log_text((char *)"Next pointer");
}
log_text((char *)" ");
log_type_and_name(ref);
log_text((char *)" ");
log_text((char *)errtype);
ENDLOG
}
static void Log_header_err(obj, loc, val, is_object, valtype, errtype)
dbref obj, loc, val;
int is_object;
const char *valtype, *errtype;
{
STARTLOG(LOG_PROBLEMS, "OBJ", "DAMAG")
log_type_and_name(obj);
if (loc != NOTHING) {
log_text((char *)" in ");
log_type_and_name(loc);
}
log_text((char *)": ");
log_text((char *)valtype);
log_text((char *)" ");
if (is_object)
log_type_and_name(val);
else
log_number(val);
log_text((char *)" ");
log_text((char *)errtype);
ENDLOG
}
static void Log_simple_err(obj, loc, errtype)
dbref obj, loc;
const char *errtype;
{
STARTLOG(LOG_PROBLEMS, "OBJ", "DAMAG")
log_type_and_name(obj);
if (loc != NOTHING) {
log_text((char *)" in ");
log_type_and_name(loc);
}
log_text((char *)": ");
log_text((char *)errtype);
ENDLOG
}
/* ---------------------------------------------------------------------------
* start_home, default_home, can_set_home, new_home, clone_home:
* Routines for validating and determining homes.
*/
dbref NDECL(start_home)
{
if (mudconf.start_home != NOTHING)
return mudconf.start_home;
return mudconf.start_room;
}
dbref NDECL(default_home)
{
if (mudconf.default_home != NOTHING)
return mudconf.default_home;
if (mudconf.start_home != NOTHING)
return mudconf.start_home;
return mudconf.start_room;
}
int can_set_home(player, thing, home)
dbref player, thing, home;
{
if (!Good_obj(player) || !Good_obj(home) || (thing == home))
return 0;
switch (Typeof(home)) {
case TYPE_PLAYER:
case TYPE_ROOM:
case TYPE_THING:
if (Going(home))
return 0;
if (Controls(player, home) || Abode(home))
return 1;
}
return 0;
}
dbref new_home(player)
dbref player;
{
dbref loc;
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(player, thing)
dbref player, thing;
{
dbref loc;
loc = Home(thing);
if (can_set_home(Owner(player), player, loc))
return loc;
return new_home(player);
}
#ifndef STANDALONE
/* ---------------------------------------------------------------------------
* update_newobjs: Update a player's most-recently-created objects.
*/
static void update_newobjs(player, obj_num, obj_type)
dbref player;
dbref obj_num;
int obj_type;
{
int i, aowner, aflags, alen;
char *newobj_str, *p, tbuf[SBUF_SIZE], *tokst;
int obj_list[4];
newobj_str = atr_get(player, A_NEWOBJS, &aowner, &aflags, &alen);
if (!*newobj_str) {
for (i = 0; i < 4; i++)
obj_list[i] = -1;
} else {
for (p = strtok_r(newobj_str, " ", &tokst), i = 0;
p && (i < 4);
p = strtok_r(NULL, " ", &tokst), i++) {
obj_list[i] = atoi(p);
}
}
free_lbuf(newobj_str);
switch (obj_type) {
case TYPE_ROOM:
obj_list[0] = obj_num;
break;
case TYPE_EXIT:
obj_list[1] = obj_num;
break;
case TYPE_THING:
obj_list[2] = obj_num;
break;
case TYPE_PLAYER:
obj_list[3] = obj_num;
break;
}
sprintf(tbuf, "%d %d %d %d", obj_list[0], obj_list[1], obj_list[2],
obj_list[3]);
atr_add_raw(player, A_NEWOBJS, tbuf);
}
/* ---------------------------------------------------------------------------
* ok_exit_name: Make sure an exit name contains no blank components.
*/
static int ok_exit_name(name)
char *name;
{
char *p, *lastp, *s;
char buff[LBUF_SIZE];
strcpy(buff, name); /* munchable buffer */
/* walk down the string, checking lengths. skip leading spaces. */
for (p = buff, lastp = buff;
(p = (char *) index(lastp, ';')) != NULL;
lastp = p) {
*p++ = '\0';
s = lastp;
while (isspace(*s))
s++;
if (strlen(s) < 1)
return 0;
}
/* check last component */
while (isspace(*lastp))
lastp++;
if (strlen(lastp) < 1)
return 0;
return 1;
}
/* ---------------------------------------------------------------------------
* create_obj: Create an object of the indicated type IF the player can
* afford it.
*/
dbref create_obj(player, objtype, name, cost)
dbref player;
int objtype, cost;
char *name;
{
dbref obj, owner, parent;
int quota, okname = 0, value, self_owned, require_inherit;
FLAG f1, f2, f3;
time_t tt;
char *buff;
const char *tname;
struct timeval obj_time;
/* First check to see whether or not we're allowed to grow the
* database any further (we must either have an object in the
* freelist, or we have to be under the limit).
*/
if ((mudstate.db_top + 1 >= mudconf.building_limit) &&
(mudstate.freelist == NOTHING)) {
notify(player, "The database building limit has been reached.");
return NOTHING;
}
value = 0;
quota = 0;
self_owned = 0;
require_inherit = 0;
switch (objtype) {
case TYPE_ROOM:
cost = mudconf.digcost;
quota = mudconf.room_quota;
f1 = mudconf.room_flags.word1;
f2 = mudconf.room_flags.word2;
f3 = mudconf.room_flags.word3;
parent = mudconf.room_parent;
okname = ok_name(name);
tname = "a room";
break;
case TYPE_THING:
if (cost < mudconf.createmin)
cost = mudconf.createmin;
if (cost > mudconf.createmax)
cost = mudconf.createmax;
quota = mudconf.thing_quota;
f1 = mudconf.thing_flags.word1;
f2 = mudconf.thing_flags.word2;
f3 = mudconf.thing_flags.word3;
parent = mudconf.thing_parent;
value = OBJECT_ENDOWMENT(cost);
okname = ok_name(name);
tname = "a thing";
break;
case TYPE_EXIT:
cost = mudconf.opencost;
quota = mudconf.exit_quota;
f1 = mudconf.exit_flags.word1;
f2 = mudconf.exit_flags.word2;
f3 = mudconf.exit_flags.word3;
parent = mudconf.exit_parent;
okname = ok_name(name) && ok_exit_name(name);
tname = "an exit";
break;
case TYPE_PLAYER:
if (cost) {
cost = mudconf.robotcost;
quota = mudconf.player_quota;
f1 = mudconf.robot_flags.word1;
f2 = mudconf.robot_flags.word2;
f3 = mudconf.robot_flags.word3;
value = 0;
tname = "a robot";
require_inherit = 1;
} else {
cost = 0;
quota = 0;
f1 = mudconf.player_flags.word1;
f2 = mudconf.player_flags.word2;
f3 = mudconf.player_flags.word3;
value = mudconf.paystart;
quota = mudconf.start_quota;
self_owned = 1;
tname = "a player";
}
parent = mudconf.player_parent;
buff = munge_space(name);
okname = badname_check(buff);
if (!okname) {
notify(player, "That name is not allowed.");
free_lbuf(buff);
return NOTHING;
}
if (okname && *buff)
okname = ok_player_name(buff);
if (okname) {
okname = (lookup_player(NOTHING, buff, 0) == NOTHING);
if (!okname) {
notify(player,
tprintf("The name %s is already taken.",
name));
free_lbuf(buff);
return NOTHING;
}
}
free_lbuf(buff);
break;
default:
LOG_SIMPLE(LOG_BUGS, "BUG", "OTYPE",
tprintf("Bad object type in create_obj: %d.",
objtype));
return NOTHING;
}
if (!okname) {
notify(player, tprintf("That's 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, objtype))
return NOTHING;
else if (player != NOTHING)
payfees(player, cost, quota, objtype);
/* 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 (Good_obj(obj) && IS_CLEAN(obj)) {
mudstate.freelist = Link(obj);
} else {
LOG_SIMPLE(LOG_PROBLEMS, "FRL", "DAMAG",
tprintf("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);
s_Parent(obj, parent);
if (mudconf.autozone && player != NOTHING)
s_Zone(obj, Zone(player));
else
s_Zone(obj, NOTHING);
s_Flags(obj, objtype | f1);
s_Flags2(obj, f2);
s_Flags3(obj, f3);
s_Owner(obj, (self_owned ? obj : owner));
s_Pennies(obj, value);
Unmark(obj);
buff = munge_space((char *)name);
s_Name(obj, buff);
free_lbuf(buff);
#ifndef NO_TIMECHECKING
obj_time.tv_sec = obj_time.tv_usec = 0;
s_Time_Used(obj, obj_time);
#endif
s_StackCount(obj, 0);
s_VarsCount(obj, 0);
s_StructCount(obj, 0);
s_InstanceCount(obj, 0);
if (objtype == TYPE_PLAYER) {
time(&tt);
buff = (char *)ctime(&tt);
buff[strlen(buff) - 1] = '\0';
atr_add_raw(obj, A_LAST, buff);
buff = alloc_sbuf("create_obj.quota");
sprintf(buff, "%d %d %d %d %d", quota, mudconf.start_room_quota,
mudconf.start_exit_quota, mudconf.start_thing_quota,
mudconf.start_player_quota);
atr_add_raw(obj, A_QUOTA, buff);
atr_add_raw(obj, A_RQUOTA, buff);
add_player_name(obj, Name(obj));
free_sbuf(buff);
s_Zone(obj, NOTHING);
if (!cost)
payfees(obj, 0, mudconf.player_quota, TYPE_PLAYER);
}
if (player != NOTHING) {
update_newobjs(player, obj, objtype);
}
return obj;
}
#endif
/* ---------------------------------------------------------------------------
* destroy_obj: Destroy an object. Assumes it has already been removed from
* all lists and has no contents or exits.
*/
void destroy_obj(player, obj)
dbref player, obj;
{
dbref owner;
int good_owner, val, quota;
#ifndef STANDALONE
char *tname;
#endif
if (!Good_obj(obj))
return;
/* Validate the owner */
owner = Owner(obj);
good_owner = Good_owner(owner);
/* Halt any pending commands (waiting or semaphore) */
#ifndef STANDALONE
if (halt_que(NOTHING, obj) > 0) {
if (good_owner && !Quiet(obj) && !Quiet(owner)) {
notify(owner, "Halted.");
}
}
nfy_que(obj, 0, NFY_DRAIN, 0);
cron_clr(obj, NOTHING);
/* Remove forwardlists, stacks, etc. from the hash tables. */
fwdlist_clr(obj);
stack_clr(obj);
xvars_clr(obj);
structure_clr(obj);
#ifdef USE_COMSYS
channel_clr(obj);
#endif
#endif /* ! STANDALONE */
/* Compensate the owner for the object */
val = 1;
quota = 1;
if (good_owner && (owner != obj)) {
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:
val = mudconf.opencost;
quota = mudconf.exit_quota;
break;
case TYPE_PLAYER:
if (Robot(obj))
val = mudconf.robotcost;
else
val = 0;
quota = mudconf.player_quota;
}
payfees(owner, -val, -quota, Typeof(obj));
#ifndef STANDALONE
if (!Quiet(owner) && !Quiet(obj))
notify(owner,
tprintf("You get back your %d %s deposit for %s(#%d).",
val, mudconf.one_coin, Name(obj), obj));
#endif
}
#ifndef STANDALONE
if ((player != NOTHING) && !Quiet(player)) {
if (good_owner && Owner(player) != owner) {
if (owner == obj) {
notify(player,
tprintf("Destroyed. %s(#%d)",
Name(obj), obj));
} else {
tname = alloc_sbuf("destroy_obj");
strcpy(tname, Name(owner));
notify(player,
tprintf("Destroyed. %s's %s(#%d)",
tname, Name(obj), obj));
free_sbuf(tname);
}
} else if (!Quiet(obj)) {
notify(player, "Destroyed.");
}
}
#endif
atr_free(obj);
s_Name(obj, NULL);
s_Flags(obj, (TYPE_GARBAGE | GOING));
s_Flags2(obj, 0);
s_Flags3(obj, 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_Zone(obj, NOTHING);
}
#ifndef STANDALONE
/* ---------------------------------------------------------------------------
* do_freelist: Grab a garbage object, and move it to the top of the freelist.
*/
void do_freelist(player, cause, key, str)
dbref player, cause;
int key;
char *str;
{
dbref i, thing;
/* We can only take a dbref; don't bother calling match_absolute() even,
* since we're dealing with the garbage pile anyway.
*/
if (*str != '#') {
notify(player, NOMATCH_MESSAGE);
return;
}
str++;
if (!*str) {
notify(player, NOMATCH_MESSAGE);
return;
}
thing = atoi(str);
if (!Good_obj(thing)) {
notify(player, NOMATCH_MESSAGE);
return;
}
/* The freelist is a linked list going from the highest-numbered
* objects to the lowest-numbered objects. We need to make sure an
* object is clean before we muck with it.
*/
if (IS_CLEAN(thing)) {
if (mudstate.freelist == thing) {
notify(player, "That object is already at the head of the freelist.");
return;
}
/* We've got to find this thing's predecessor so we avoid
* circular chaining.
*/
DO_WHOLE_DB(i) {
if (Link(i) == thing) {
if (IS_CLEAN(i)) {
s_Link(i, Link(thing));
break; /* shouldn't have more than one linkage */
} else {
notify(player, "Unable to relink freelist at this time.");
return;
}
}
}
s_Link(thing, mudstate.freelist);
mudstate.freelist = thing;
notify(player, "Object placed at the head of the freelist.");
} else {
notify(player, "That object is not clean garbage.");
}
}
#endif
/* ---------------------------------------------------------------------------
* make_freelist: Build a freelist
*/
static void NDECL(make_freelist)
{
dbref i;
mudstate.freelist = NOTHING;
DO_WHOLE_DB(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(thing)
dbref thing;
{
dbref curr, temp;
SAFE_DOLIST(curr, temp, Contents(thing)) {
if (!Controls(thing, curr) &&
Has_location(curr) && Key(curr)) {
move_via_generic(curr, HOME, NOTHING, 0);
}
}
}
/* ---------------------------------------------------------------------------
* empty_obj, purge_going: Get rid of GOING objects in the db.
*/
void empty_obj(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,
"Funny object type in contents list of GOING location. Flush terminated.");
break;
} else if (Location(targ) != obj) {
Log_header_err(targ, obj, Location(targ), 1,
"Location",
"indicates object really in another location during cleanup of GOING location. Flush terminated.");
break;
} else {
ZAP_LOC(targ);
if (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)) {
Log_simple_err(targ, obj,
"Funny object type in exit list of GOING location. Flush terminated.");
break;
} else if (Exits(targ) != obj) {
Log_header_err(targ, obj, Exits(targ), 1,
"Location",
"indicates exit really in another location during cleanup of GOING location. Flush terminated.");
break;
} else {
destroy_obj(NOTHING, targ);
}
}
}
/* ---------------------------------------------------------------------------
* destroy_exit, destroy_thing, destroy_player
*/
void destroy_exit(exit)
dbref exit;
{
dbref loc;
loc = Exits(exit);
s_Exits(loc, remove_first(Exits(loc), exit));
destroy_obj(NOTHING, exit);
}
void destroy_thing(thing)
dbref thing;
{
move_via_generic(thing, NOTHING, Owner(thing), 0);
empty_obj(thing);
destroy_obj(NOTHING, thing);
}
void destroy_player(victim)
dbref victim;
{
#ifndef STANDALONE
dbref aowner, player;
int count, aflags, alen;
char *buf;
/* Bye bye... */
player = (dbref) atoi(atr_get_raw(victim, A_DESTROYER));
boot_off(victim, (char *)"You have been destroyed!");
halt_que(victim, NOTHING);
count = chown_all(victim, player, player, 0);
/* Remove the name from the name hash table */
delete_player_name(victim, Name(victim));
buf = atr_pget(victim, A_ALIAS, &aowner, &aflags, &alen);
delete_player_name(victim, buf);
free_lbuf(buf);
move_via_generic(victim, NOTHING, player, 0);
#ifdef USE_MAIL
do_mail_clear(victim, NULL);
do_mail_purge(victim);
#endif
#ifdef USE_COMSYS
comsys_chown(victim, Owner(player));
#endif
destroy_obj(NOTHING, victim);
notify_quiet(player, tprintf("(%d objects @chowned to you)", count));
#endif
}
static void NDECL(purge_going)
{
dbref i;
DO_WHOLE_DB(i) {
if (!Going(i))
continue;
switch (Typeof(i)) {
case TYPE_PLAYER:
destroy_player(i);
break;
case TYPE_ROOM:
/* Room scheduled for destruction... do it */
empty_obj(i);
destroy_obj(NOTHING, 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,
"GOING object with unexpected type. Destroyed.");
destroy_obj(NOTHING, i);
}
}
}
/* ---------------------------------------------------------------------------
* check_dead_refs: Look for references to GOING or illegal objects.
*/
static void check_pennies(thing, limit, qual)
dbref thing;
int limit;
const char *qual;
{
int j;
if (Going(thing))
return;
j = Pennies(thing);
if (isRoom(thing) || isExit(thing)) {
if (j) {
Log_header_err(thing, NOTHING, j, 0,
qual, "is strange. Reset.");
s_Pennies(j, 0);
}
} else if (j == 0) {
Log_header_err(thing, NOTHING, j, 0, qual, "is zero.");
} else if (j < 0) {
Log_header_err(thing, NOTHING, j, 0, qual, "is negative.");
} else if (j > limit) {
Log_header_err(thing, NOTHING, j, 0, qual, "is excessive.");
}
}
static NDECL(void check_dead_refs)
{
dbref targ, owner, i, j;
int aflags, dirty;
char *str;
FWDLIST *fp;
DO_WHOLE_DB(i) {
/* Check the parent */
targ = Parent(i);
if (Good_obj(targ)) {
if (Going(targ)) {
s_Parent(i, NOTHING);
#ifndef STANDALONE
owner = Owner(i);
if (Good_owner(owner) &&
!Quiet(i) && !Quiet(owner)) {
notify(owner,
tprintf("Parent cleared on %s(#%d)",
Name(i), i));
}
#else
Log_header_err(i, Location(i), targ, 1,
"Parent", "is invalid. Cleared.");
#endif
}
} else if (targ != NOTHING) {
Log_header_err(i, Location(i), targ, 1,
"Parent", "is invalid. Cleared.");
s_Parent(i, NOTHING);
}
/* Check the zone */
targ = Zone(i);
if (Good_obj(targ)) {
if (Going(targ)) {
s_Zone(i, NOTHING);
#ifndef STANDALONE
owner = Owner(i);
if (Good_owner(owner) &&
!Quiet(i) && !Quiet(owner)) {
notify(owner,
tprintf("Zone cleared on %s(#%d)",
Name(i), i));
}
#else
Log_header_err(i, Location(i), targ, 1,
"Zone", "is invalid. Cleared.");
#endif
}
} else if (targ != NOTHING) {
Log_header_err(i, Location(i), targ, 1,
"Zone", "is invalid. Cleared.");
s_Zone(i, NOTHING);
}
switch (Typeof(i)) {
case TYPE_PLAYER:
case TYPE_THING:
if (Going(i))
break;
/* Check the home */
targ = Home(i);
if (Good_obj(targ)) {
if (Going(targ)) {
s_Home(i, new_home(i));
#ifndef STANDALONE
owner = Owner(i);
if (Good_owner(owner) &&
!Quiet(i) && !Quiet(owner)) {
notify(owner,
tprintf("Home reset on %s(#%d)",
Name(i), i));
}
#else
Log_header_err(i, Location(i), targ, 1,
"Home",
"is invalid. Reset.");
#endif
}
} else if (targ != NOTHING) {
Log_header_err(i, Location(i), targ, 1,
"Home", "is invalid. Cleared.");
s_Home(i, new_home(i));
}
/* Check the location */
targ = Location(i);
if (!Good_obj(targ)) {
Log_pointer_err(NOTHING, i, NOTHING, targ,
"Location",
"is invalid. Moved to home.");
ZAP_LOC(i);
move_object(i, HOME);
}
/* Check for self-referential Next() */
if (Next(i) == i) {
Log_simple_err(i, NOTHING,
"Next points to self. Next cleared.");
s_Next(i, NOTHING);
}
if (check_type & DBCK_FULL) {
/* Check wealth or value */
targ = OBJECT_ENDOWMENT(mudconf.createmax);
if (OwnsOthers(i)) {
targ += mudconf.paylimit;
check_pennies(i, targ, "Wealth");
} else {
check_pennies(i, targ, "Value");
}
}
break;
case TYPE_ROOM:
/* Check the dropto */
targ = Dropto(i);
if (Good_obj(targ)) {
if (Going(targ)) {
s_Dropto(i, NOTHING);
#ifndef STANDALONE
owner = Owner(i);
if (Good_owner(owner) &&
!Quiet(i) && !Quiet(owner)) {
notify(owner,
tprintf("Dropto removed from %s(#%d)",
Name(i), i));
}
#else
Log_header_err(i, NOTHING, targ, 1,
"Dropto",
"is invalid. Removed.");
#endif
}
} else if ((targ != NOTHING) && (targ != HOME)) {
Log_header_err(i, NOTHING, targ, 1,
"Dropto", "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), 1,
"Next pointer",
"should be NOTHING. Reset.");
s_Next(i, NOTHING);
}
/* LINK should be null */
if (Link(i) != NOTHING) {
Log_header_err(i, NOTHING, Link(i), 1,
"Link pointer ",
"should be NOTHING. Reset.");
s_Link(i, NOTHING);
}
/* Check value */
check_pennies(i, 1, "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) || (targ == AMBIGUOUS)) {
/* null case, always valid */
} else if (targ != NOTHING) {
Log_header_err(i, Exits(i), targ, 1,
"Destination",
"is invalid. Exit destroyed.");
s_Going(i);
} else {
if (!Has_contents(targ)) {
Log_header_err(i, Exits(i), targ, 1,
"Destination",
"is not a valid type. Exit destroyed.");
s_Going(i);
}
}
/* Check for self-referential Next() */
if (Next(i) == i) {
Log_simple_err(i, NOTHING,
"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), 1, "Contents",
"should be NOTHING. Reset.");
s_Contents(i, NOTHING);
}
/* LINK should be null */
if (Link(i) != NOTHING) {
Log_header_err(i, Exits(i), Link(i), 1,
"Link",
"should be NOTHING. Reset.");
s_Link(i, NOTHING);
}
/* Check value */
check_pennies(i, 1, "Value");
}
break;
case TYPE_GARBAGE:
break;
default:
/* Funny object type, destroy it */
Log_simple_err(i, NOTHING,
"Funny object type. Destroyed.");
destroy_obj(NOTHING, i);
}
/* Check forwardlist */
dirty = 0;
if (H_Fwdlist(i) && ((fp = fwdlist_get(i)) != NULL)) {
for (j = 0; j < fp->count; j++) {
targ = fp->data[j];
if (Good_obj(targ) && Going(targ)) {
fp->data[j] = NOTHING;
dirty = 1;
} else if (!Good_obj(targ) &&
(targ != NOTHING)) {
fp->data[j] = NOTHING;
dirty = 1;
}
}
}
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);
}
/* Check owner */
owner = Owner(i);
if (!Good_obj(owner)) {
Log_header_err(i, NOTHING, owner, 1,
"Owner", "is invalid. Set to GOD.");
owner = GOD;
s_Owner(i, owner);
#ifndef STANDALONE
halt_que(NOTHING, i);
#endif
s_Halted(i);
} else if (check_type & DBCK_FULL) {
if (Going(owner)) {
Log_header_err(i, NOTHING, owner, 1,
"Owner", "is set GOING. Set to GOD.");
s_Owner(i, owner);
#ifndef STANDALONE
halt_que(NOTHING, i);
#endif
s_Halted(i);
} else if (!OwnsOthers(owner)) {
Log_header_err(i, NOTHING, owner, 1,
"Owner", "is not a valid owner type.");
} else if (isPlayer(i) && (owner != i)) {
Log_header_err(i, NOTHING, owner, 1,
"Player", "is the owner instead of the player.");
}
}
if (check_type & DBCK_FULL) {
/* Check for wizards */
if (Wizard(i)) {
if (isPlayer(i)) {
Log_simple_err(i, NOTHING,
"Player is a WIZARD.");
}
if (!Wizard(Owner(i))) {
Log_header_err(i, NOTHING, Owner(i), 1,
"Owner",
"of a WIZARD object is not a wizard");
}
}
}
}
}
/* ---------------------------------------------------------------------------
* 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 - NULL 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(loc)
dbref loc;
{
dbref exit, back, temp, exitloc, dest;
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 */
back = NOTHING;
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, "Exit list",
"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,
"Exitlist member",
"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(NOTHING, exit);
exit = temp;
continue;
} else if (Marked(exit)) {
/* Already in another list - terminate chain */
Log_pointer_err(back, loc, NOTHING, exit,
"Exitlist member",
"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 != AMBIGUOUS) && (dest != NOTHING)) {
/* Destination is not in the db. Null it. */
Log_pointer_err(back, loc, NOTHING, exit,
"Destination", "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, "",
"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, 1,
"Not on chain for location",
"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)) &&
(temp != Owner(Location(exit)))) {
Log_header_err(exit, loc, temp, 1,
"Owner",
"does not own either the source or destination.");
}
}
Mark(exit);
back = exit;
exit = Next(exit);
}
}
return;
}
static void NDECL(check_exit_chains)
{
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,
"Disconnected exit. Destroyed.");
destroy_obj(NOTHING, 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 - NULL 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 FDECL(check_loc_contents, (dbref));
static void check_misplaced_obj(obj, back, loc)
dbref *obj, back, 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, "",
"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), 1,
"Location", "is invalid. Reset.");
s_Contents(*obj, loc);
}
return;
}
static void check_loc_contents(loc)
dbref loc;
{
dbref obj, back, temp;
if (!Good_obj(loc))
return;
/* Only check players, rooms, and things that aren't GOING */
if (isExit(loc) || Going(loc))
return;
/* Check all the exits */
back = NOTHING;
obj = Contents(loc);
while (obj != NOTHING) {
if (!Good_obj(obj)) {
/* A bad pointer - terminate chain */
Log_pointer_err(back, loc, NOTHING, obj,
"Contents list", "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, "",
"is not a player or thing. Cleared.");
if (back != NOTHING) {
s_Next(back, NOTHING);
} else {
s_Contents(loc, NOTHING);
}
obj = NOTHING;
} else if (Going(obj) && (Typeof(obj) == TYPE_GARBAGE)) {
/* Going - silently filter out */
temp = Next(obj);
if (back != NOTHING) {
s_Next(back, temp);
} else {
s_Contents(loc, temp);
}
destroy_obj(NOTHING, obj);
obj = temp;
continue;
} 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
* nonwiz. Just warn if we find one.
*/
if (Wizard(obj) && !Wizard(loc)) {
if (Commer(obj)) {
Log_simple_err(obj, loc,
"Wizard command handling object inside nonwizard.");
}
}
/* Check for nonwizard objects inside wizard
* objects.
*/
if (Wizard(loc) &&
!Wizard(obj) && !Wizard(Owner(obj))) {
Log_simple_err(obj, loc,
"Nonwizard object inside wizard.");
}
}
Mark(obj);
back = obj;
obj = Next(obj);
}
}
return;
}
static void NDECL(check_contents_chains)
{
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),
"Orphaned object, moved home.");
ZAP_LOC(i);
move_via_generic(i, HOME, NOTHING, 0);
}
}
/* ---------------------------------------------------------------------------
* mark_place, check_floating: Look for floating rooms not set FLOATING.
*/
static void mark_place(loc)
dbref loc;
{
dbref exit;
/* If already marked, exit. Otherwise set marked. */
if (!Good_obj(loc))
return;
if (Marked(loc))
return;
Mark(loc);
/* Visit all places you can get to via exits from here. */
for (exit = Exits(loc); exit != NOTHING; exit = Next(exit)) {
if (Good_obj(Location(exit)))
mark_place(Location(exit));
}
}
static NDECL(void check_floating)
{
dbref owner, i;
/* Mark everyplace you can get to via exits from the starting room */
Unmark_all(i);
mark_place(mudconf.start_room);
/* Look for rooms not marked and not set FLOATING */
DO_WHOLE_DB(i) {
if (isRoom(i) && !Floating(i) && !Going(i) && !Marked(i)) {
owner = Owner(i);
#ifndef STANDALONE
if (Good_owner(owner)) {
notify(owner, tprintf(
"You own a floating room: %s(#%d)",
Name(i), i));
}
#else
Log_simple_err(i, NOTHING, "Disconnected room.");
#endif
}
}
}
/* ---------------------------------------------------------------------------
* do_dbck: Perform a database consistency check and clean up damage.
*/
void do_dbck(player, cause, key)
dbref player, cause;
int key;
{
check_type = key;
make_freelist();
check_dead_refs();
check_exit_chains();
check_contents_chains();
check_floating();
purge_going();
#ifndef STANDALONE
if (player != NOTHING) {
alarm(1);
if (!Quiet(player))
notify(player, "Done.");
}
#endif
}