/* command.c - command parser and support routines */

#include "autoconf.h"
#include "copyright.h"
#ifndef	lint
static char RCSid[] = "$Id: command.c,v 1.11 1995/03/20 23:59:47 ambar Exp $";
USE(RCSid);
#endif

#include "externs.h"
#include "interface.h"
#include "command.h"
#include "functions.h"
#include "match.h"
#include "attrs.h"
#include "flags.h"
#include "alloc.h"
#include "vattr.h"

extern void FDECL(list_cf_access, (dbref));
extern void FDECL(list_siteinfo, (dbref));

#ifdef CACHE_OBJS
#define CACHING "object"
#else
#define CACHING "attribute"
#endif

/* Take care of all the assorted problems associated with getrusage(). */

#ifdef hpux
#define HAVE_GETRUSAGE 1
#include <sys/syscall.h>
#define getrusage(x,p)   syscall(SYS_GETRUSAGE,x,p)
#endif

#ifdef _SEQUENT_
#define HAVE_GET_PROCESS_STATS 1
#include <sys/procstats.h>
#endif

/* ---------------------------------------------------------------------------
 * Switch tables for the various commands.
 */

#define	SW_MULTIPLE	0x80000000	/* This sw may be spec'd w/others */
#define	SW_GOT_UNIQUE	0x40000000	/* Already have a unique option */
#define SW_NOEVAL	0x20000000	/* Don't parse args before calling handler */
 /* (typically via a switch alias) */

NAMETAB attrib_sw[] =
{
    {(char *) "access", 1, CA_GOD, ATTRIB_ACCESS},
    {(char *) "delete", 1, CA_GOD, ATTRIB_DELETE},
    {(char *) "rename", 1, CA_GOD, ATTRIB_RENAME},
    {NULL, 0, 0, 0}};

NAMETAB boot_sw[] =
{
    {(char *) "port", 1, CA_WIZARD, BOOT_PORT | SW_MULTIPLE},
    {(char *) "quiet", 1, CA_WIZARD, BOOT_QUIET | SW_MULTIPLE},
    {NULL, 0, 0, 0}};

NAMETAB clone_sw[] =
{
    {(char *) "cost", 1, CA_PUBLIC, CLONE_SET_COST},
    {(char *) "inherit", 3, CA_PUBLIC, CLONE_INHERIT | SW_MULTIPLE},
    {(char *) "inventory", 3, CA_PUBLIC, CLONE_INVENTORY},
    {(char *) "location", 1, CA_PUBLIC, CLONE_LOCATION},
    {(char *) "parent", 2, CA_PUBLIC, CLONE_PARENT | SW_MULTIPLE},
    {(char *) "preserve", 2, CA_WIZARD, CLONE_PRESERVE | SW_MULTIPLE},
    {NULL, 0, 0, 0}};

NAMETAB destroy_sw[] =
{
    {(char *) "override", 8, CA_PUBLIC, DEST_OVERRIDE},
    {NULL, 0, 0, 0}};

NAMETAB dig_sw[] =
{
    {(char *) "teleport", 1, CA_PUBLIC, DIG_TELEPORT},
    {NULL, 0, 0, 0}};

NAMETAB doing_sw[] =
{
    {(char *) "header", 1, CA_WIZARD, DOING_HEADER},
    {(char *) "message", 1, CA_PUBLIC, DOING_MESSAGE},
    {(char *) "poll", 1, CA_PUBLIC, DOING_POLL},
    {NULL, 0, 0, 0}};

NAMETAB dolist_sw[] =
{
    {(char *) "delimit", 1, CA_PUBLIC, DOLIST_DELIMIT},
    {(char *) "space", 1, CA_PUBLIC, DOLIST_SPACE},
    {(char *) "notify", 1, CA_PUBLIC, DOLIST_NOTIFY | SW_MULTIPLE },
    {NULL, 0, 0, 0,}};

NAMETAB drop_sw[] =
{
    {(char *) "quiet", 1, CA_PUBLIC, DROP_QUIET},
    {NULL, 0, 0, 0}};

NAMETAB dump_sw[] =
{
    {(char *) "structure", 1, CA_WIZARD, DUMP_STRUCT | SW_MULTIPLE},
    {(char *) "text", 1, CA_WIZARD, DUMP_TEXT | SW_MULTIPLE},
    {NULL, 0, 0, 0}};

NAMETAB emit_sw[] =
{
    {(char *) "here", 1, CA_PUBLIC, SAY_HERE | SW_MULTIPLE},
    {(char *) "room", 1, CA_PUBLIC, SAY_ROOM | SW_MULTIPLE},
#ifdef PUEBLO_SUPPORT
    {(char *) "html", 1, CA_PUBLIC, SAY_HTML | SW_MULTIPLE},
#endif /* PUEBLO_SUPPORT */
    {NULL, 0, 0, 0}};

NAMETAB enter_sw[] =
{
    {(char *) "quiet", 1, CA_PUBLIC, MOVE_QUIET},
    {NULL, 0, 0, 0}};

NAMETAB examine_sw[] =
{
    {(char *) "brief", 1, CA_PUBLIC, EXAM_BRIEF},
    {(char *) "debug", 1, CA_WIZARD, EXAM_DEBUG},
    {(char *) "full", 1, CA_PUBLIC, EXAM_LONG},
    {(char *) "parent", 1, CA_PUBLIC, EXAM_PARENT},
    {NULL, 0, 0, 0}};

NAMETAB femit_sw[] =
{
    {(char *) "here", 1, CA_PUBLIC, PEMIT_HERE | SW_MULTIPLE},
    {(char *) "room", 1, CA_PUBLIC, PEMIT_ROOM | SW_MULTIPLE},
    {NULL, 0, 0, 0}};

NAMETAB fixdb_sw[] =
{
/* {(char *)"add_pname",1,	CA_GOD,		FIXDB_ADD_PN}, */
    {(char *) "contents", 1, CA_GOD, FIXDB_CON},
    {(char *) "exits", 1, CA_GOD, FIXDB_EXITS},
    {(char *) "location", 1, CA_GOD, FIXDB_LOC},
    {(char *) "next", 1, CA_GOD, FIXDB_NEXT},
    {(char *) "owner", 1, CA_GOD, FIXDB_OWNER},
    {(char *) "pennies", 1, CA_GOD, FIXDB_PENNIES},
    {(char *) "rename", 1, CA_GOD, FIXDB_NAME},
/* {(char *)"rm_pname",	1,	CA_GOD,		FIXDB_DEL_PN}, */
    {NULL, 0, 0, 0}};

NAMETAB fpose_sw[] =
{
    {(char *) "default", 1, CA_PUBLIC, 0},
    {(char *) "nospace", 1, CA_PUBLIC, SAY_NOSPACE},
    {NULL, 0, 0, 0}};

NAMETAB function_sw[] =
{
    {(char *) "privileged", 1, CA_WIZARD, FN_PRIV},
    {NULL, 0, 0, 0}};

NAMETAB get_sw[] =
{
    {(char *) "quiet", 1, CA_PUBLIC, GET_QUIET},
    {NULL, 0, 0, 0}};

NAMETAB give_sw[] =
{
    {(char *) "quiet", 1, CA_WIZARD, GIVE_QUIET},
    {NULL, 0, 0, 0}};

NAMETAB goto_sw[] =
{
    {(char *) "quiet", 1, CA_PUBLIC, MOVE_QUIET},
    {NULL, 0, 0, 0}};

NAMETAB halt_sw[] =
{
    {(char *) "all", 1, CA_WIZARD, HALT_ALL},
    {NULL, 0, 0, 0}};

NAMETAB leave_sw[] =
{
    {(char *) "quiet", 1, CA_PUBLIC, MOVE_QUIET},
    {NULL, 0, 0, 0}};

NAMETAB listmotd_sw[] =
{
    {(char *) "brief", 1, CA_WIZARD, MOTD_BRIEF},
    {NULL, 0, 0, 0}};

NAMETAB lock_sw[] =
{
    {(char *) "controllock", 1, CA_PUBLIC, A_LCONTROL},
    {(char *) "defaultlock", 1, CA_PUBLIC, A_LOCK},
    {(char *) "droplock", 1, CA_PUBLIC, A_LDROP},
    {(char *) "enterlock", 1, CA_PUBLIC, A_LENTER},
    {(char *) "givelock", 1, CA_PUBLIC, A_LGIVE},
    {(char *) "leavelock", 2, CA_PUBLIC, A_LLEAVE},
    {(char *) "linklock", 2, CA_PUBLIC, A_LLINK},
    {(char *) "pagelock", 3, CA_PUBLIC, A_LPAGE},
    {(char *) "parentlock", 3, CA_PUBLIC, A_LPARENT},
    {(char *) "receivelock", 1, CA_PUBLIC, A_LRECEIVE},
    {(char *) "teloutlock", 2, CA_PUBLIC, A_LTELOUT},
    {(char *) "tportlock", 2, CA_PUBLIC, A_LTPORT},
    {(char *) "uselock", 1, CA_PUBLIC, A_LUSE},
    {(char *) "userlock", 4, CA_PUBLIC, A_LUSER},
    {NULL, 0, 0, 0}};

NAMETAB look_sw[] =
{
    {(char *) "outside", 1, CA_PUBLIC, LOOK_OUTSIDE},
    {NULL, 0, 0, 0}};

NAMETAB mark_sw[] =
{
    {(char *) "set", 1, CA_PUBLIC, MARK_SET},
    {(char *) "clear", 1, CA_PUBLIC, MARK_CLEAR},
    {NULL, 0, 0, 0}};

NAMETAB markall_sw[] =
{
    {(char *) "set", 1, CA_PUBLIC, MARK_SET},
    {(char *) "clear", 1, CA_PUBLIC, MARK_CLEAR},
    {NULL, 0, 0, 0}};

NAMETAB motd_sw[] =
{
    {(char *) "brief", 1, CA_WIZARD, MOTD_BRIEF | SW_MULTIPLE},
    {(char *) "connect", 1, CA_WIZARD, MOTD_ALL},
    {(char *) "down", 1, CA_WIZARD, MOTD_DOWN},
    {(char *) "full", 1, CA_WIZARD, MOTD_FULL},
    {(char *) "list", 1, CA_PUBLIC, MOTD_LIST},
    {(char *) "wizard", 1, CA_WIZARD, MOTD_WIZ},
    {NULL, 0, 0, 0}};

NAMETAB notify_sw[] =
{
    {(char *) "all", 1, CA_PUBLIC, NFY_NFYALL},
    {(char *) "first", 1, CA_PUBLIC, NFY_NFY},
    {NULL, 0, 0, 0}};

NAMETAB open_sw[] =
{
    {(char *) "inventory", 1, CA_PUBLIC, OPEN_INVENTORY},
    {(char *) "location", 1, CA_PUBLIC, OPEN_LOCATION},
    {NULL, 0, 0, 0}};

NAMETAB parent_sw[] =
{
    {(char *) "parent", 1, CA_PUBLIC, PARENT_PARENT},
    {(char *) "zone", 1, CA_PUBLIC, PARENT_ZONE},
    {NULL, 0, 0, 0}};

NAMETAB pemit_sw[] =
{
    {(char *) "contents", 1, CA_PUBLIC, PEMIT_CONTENTS},
    {(char *) "list", 1, CA_PUBLIC, PEMIT_LIST | SW_MULTIPLE},
    {(char *) "object", 1, CA_PUBLIC, 0},
    {(char *) "noeval", 1, CA_PUBLIC, SW_NOEVAL | SW_MULTIPLE},
#ifdef PUEBLO_SUPPORT
    {(char *) "html", 1, CA_PUBLIC, PEMIT_HTML | SW_MULTIPLE},
#endif /* PUEBLO_SUPPORT */
    {NULL, 0, 0, 0}};

NAMETAB pose_sw[] =
{
    {(char *) "default", 1, CA_PUBLIC, 0},
    {(char *) "nospace", 1, CA_PUBLIC, SAY_NOSPACE},
    {NULL, 0, 0, 0}};

NAMETAB ps_sw[] =
{
    {(char *) "all", 1, CA_WIZARD, PS_ALL | SW_MULTIPLE},
    {(char *) "brief", 1, CA_PUBLIC, PS_BRIEF},
    {(char *) "long", 1, CA_PUBLIC, PS_LONG},
    {(char *) "summary", 1, CA_PUBLIC, PS_SUMM},
    {NULL, 0, 0, 0}};

NAMETAB quota_sw[] =
{
    {(char *) "all", 1, CA_GOD, QUOTA_ALL | SW_MULTIPLE},
    {(char *) "fix", 1, CA_WIZARD, QUOTA_FIX},
    {(char *) "remaining", 1, CA_WIZARD, QUOTA_REM | SW_MULTIPLE},
    {(char *) "set", 1, CA_WIZARD, QUOTA_SET},
    {(char *) "total", 1, CA_WIZARD, QUOTA_TOT | SW_MULTIPLE},
    {(char *) "room", 1, CA_WIZARD, QUOTA_ROOM | SW_MULTIPLE},
    {(char *) "exit", 1, CA_WIZARD, QUOTA_EXIT | SW_MULTIPLE},
    {(char *) "thing", 1, CA_WIZARD, QUOTA_THING | SW_MULTIPLE},
    {(char *) "player", 1, CA_WIZARD, QUOTA_PLAYER | SW_MULTIPLE},
    {NULL, 0, 0, 0}};

NAMETAB rwho_sw[] =
{
    {(char *) "start", 3, CA_PUBLIC, RWHO_START},
    {(char *) "stop", 3, CA_PUBLIC, RWHO_STOP},
    {NULL, 0, 0, 0}};

NAMETAB set_sw[] =
{
    {(char *) "quiet", 1, CA_PUBLIC, SET_QUIET},
    {NULL, 0, 0, 0}};

NAMETAB shutdown_sw[] =
{
    {(char *) "abort", 1, CA_WIZARD, SHUTDN_COREDUMP},
    {NULL, 0, 0, 0}};

NAMETAB stats_sw[] =
{
    {(char *) "all", 1, CA_PUBLIC, STAT_ALL},
    {(char *) "me", 1, CA_PUBLIC, STAT_ME},
    {(char *) "player", 1, CA_PUBLIC, STAT_PLAYER},
    {NULL, 0, 0, 0}};

NAMETAB sweep_sw[] =
{
    {(char *) "commands", 3, CA_PUBLIC, SWEEP_COMMANDS | SW_MULTIPLE},
    {(char *) "connected", 3, CA_PUBLIC, SWEEP_CONNECT | SW_MULTIPLE},
    {(char *) "exits", 1, CA_PUBLIC, SWEEP_EXITS | SW_MULTIPLE},
    {(char *) "here", 1, CA_PUBLIC, SWEEP_HERE | SW_MULTIPLE},
    {(char *) "inventory", 1, CA_PUBLIC, SWEEP_ME | SW_MULTIPLE},
    {(char *) "listeners", 1, CA_PUBLIC, SWEEP_LISTEN | SW_MULTIPLE},
    {(char *) "players", 1, CA_PUBLIC, SWEEP_PLAYER | SW_MULTIPLE},
    {NULL, 0, 0, 0}};

NAMETAB switch_sw[] =
{
    {(char *) "all", 1, CA_PUBLIC, SWITCH_ANY},
    {(char *) "default", 1, CA_PUBLIC, SWITCH_DEFAULT},
    {(char *) "first", 1, CA_PUBLIC, SWITCH_ONE},
    {NULL, 0, 0, 0}};

NAMETAB toad_sw[] =
{
    {(char *) "no_chown", 1, CA_WIZARD, TOAD_NO_CHOWN | SW_MULTIPLE},
    {NULL, 0, 0, 0}};

NAMETAB trig_sw[] =
{
    {(char *) "quiet", 1, CA_PUBLIC, TRIG_QUIET},
    {NULL, 0, 0, 0}};

NAMETAB wall_sw[] =
{
    {(char *) "emit", 1, CA_WIZARD, SAY_WALLEMIT},
    {(char *) "no_prefix", 1, CA_WIZARD, SAY_NOTAG | SW_MULTIPLE},
    {(char *) "pose", 1, CA_WIZARD, SAY_WALLPOSE},
    {(char *) "wizard", 1, CA_WIZARD, SAY_WIZSHOUT | SW_MULTIPLE},
    {NULL, 0, 0, 0}};

NAMETAB warp_sw[] =
{
    {(char *) "check", 1, CA_WIZARD, TWARP_CLEAN | SW_MULTIPLE},
    {(char *) "dump", 1, CA_WIZARD, TWARP_DUMP | SW_MULTIPLE},
    {(char *) "idle", 1, CA_WIZARD, TWARP_IDLE | SW_MULTIPLE},
    {(char *) "queue", 1, CA_WIZARD, TWARP_QUEUE | SW_MULTIPLE},
    {(char *) "rwho", 1, CA_WIZARD, TWARP_RWHO | SW_MULTIPLE},
    {NULL, 0, 0, 0}};


/* ---------------------------------------------------------------------------
 * Command table: Definitions for builtin commands, used to build the command
 * hash table.
 *
 * Format:  Name		Switches	Permissions Needed
 *	Key (if any)	Calling Seq			Handler
 */

CMDENT command_table[] =
{
    {(char *) "@@", NULL, 0,
     0, CS_NO_ARGS, do_comment},
    {(char *) "@admin", NULL, CA_GOD,
     0, CS_TWO_ARG | CS_INTERP, do_admin},
    {(char *) "@alias", NULL, CA_NO_GUEST | CA_NO_SLAVE,
     0, CS_TWO_ARG, do_alias},
    {(char *) "@apply_marked", NULL, CA_WIZARD | CA_GBL_INTERP,
     0, CS_ONE_ARG | CS_CMDARG | CS_NOINTERP | CS_STRIP_AROUND,
     do_apply_marked},
    {(char *) "@attribute", attrib_sw, CA_GOD,
     0, CS_TWO_ARG | CS_INTERP, do_attribute},
    {(char *) "@boot", boot_sw, CA_WIZARD,
     0, CS_ONE_ARG | CS_INTERP, do_boot},
    {(char *) "@chown", NULL,
     CA_NO_SLAVE | CA_NO_GUEST | CA_GBL_BUILD,
     CHOWN_ONE, CS_TWO_ARG | CS_INTERP, do_chown},
    {(char *) "@chownall", NULL, CA_WIZARD | CA_GBL_BUILD,
     CHOWN_ALL, CS_TWO_ARG | CS_INTERP, do_chownall},
    {(char *) "@clone", clone_sw,
     CA_NO_SLAVE | CA_GBL_BUILD | CA_CONTENTS | CA_NO_GUEST,
     0, CS_TWO_ARG | CS_INTERP, do_clone},
    {(char *) "@create", NULL,
     CA_NO_SLAVE | CA_GBL_BUILD | CA_CONTENTS | CA_NO_GUEST,
     0, CS_TWO_ARG | CS_INTERP, do_create},
    {(char *) "@cut", NULL, CA_WIZARD | CA_LOCATION,
     0, CS_ONE_ARG | CS_INTERP, do_cut},
    {(char *) "@dbck", NULL, CA_WIZARD,
     0, CS_NO_ARGS, do_dbck},
    {(char *) "@decompile", NULL, 0,
     0, CS_TWO_ARG | CS_INTERP, do_decomp},
    {(char *) "@destroy", destroy_sw,
     CA_NO_SLAVE | CA_NO_GUEST | CA_GBL_BUILD,
     DEST_ONE, CS_ONE_ARG | CS_INTERP, do_destroy},
    {(char *) "@dig", dig_sw,
     CA_NO_SLAVE | CA_NO_GUEST | CA_GBL_BUILD,
     0, CS_TWO_ARG | CS_ARGV | CS_INTERP, do_dig},
    {(char *) "@disable", NULL, CA_WIZARD,
     GLOB_DISABLE, CS_ONE_ARG, do_global},
    {(char *) "@doing", doing_sw, CA_PLAYER,
     0, CS_ONE_ARG, do_doing},
    {(char *) "@dolist", dolist_sw, CA_GBL_INTERP,
     0, CS_TWO_ARG | CS_CMDARG | CS_NOINTERP | CS_STRIP_AROUND,
     do_dolist},
    {(char *) "@drain", NULL,
     CA_GBL_INTERP | CA_NO_SLAVE | CA_NO_GUEST,
     NFY_DRAIN, CS_TWO_ARG, do_notify},
    {(char *) "@dump", dump_sw, CA_WIZARD,
     0, CS_NO_ARGS, do_dump},
    {(char *) "@edit", NULL, CA_NO_SLAVE | CA_NO_GUEST,
     0, CS_TWO_ARG | CS_ARGV | CS_STRIP_AROUND,
     do_edit},
    {(char *) "@emit", emit_sw,
     CA_LOCATION | CA_NO_GUEST | CA_NO_SLAVE,
     SAY_EMIT, CS_ONE_ARG | CS_INTERP, do_say},
    {(char *) "@enable", NULL, CA_WIZARD,
     GLOB_ENABLE, CS_ONE_ARG, do_global},
    {(char *) "@entrances", NULL, CA_NO_GUEST,
     0, CS_ONE_ARG | CS_INTERP, do_entrances},
    {(char *) "@femit", femit_sw,
     CA_LOCATION | CA_NO_GUEST | CA_NO_SLAVE,
     PEMIT_FEMIT, CS_TWO_ARG | CS_INTERP, do_pemit},
    {(char *) "@find", NULL, 0,
     0, CS_ONE_ARG | CS_INTERP, do_find},
    {(char *) "@fixdb", fixdb_sw, CA_GOD,
     0, CS_TWO_ARG | CS_INTERP, do_fixdb},
    {(char *) "@force", NULL,
     CA_NO_SLAVE | CA_GBL_INTERP | CA_NO_GUEST,
     FRC_COMMAND, CS_TWO_ARG | CS_INTERP | CS_CMDARG, do_force},
    {(char *) "@fpose", fpose_sw, CA_LOCATION | CA_NO_SLAVE,
     PEMIT_FPOSE, CS_TWO_ARG | CS_INTERP, do_pemit},
    {(char *) "@fsay", NULL, CA_LOCATION | CA_NO_SLAVE,
     PEMIT_FSAY, CS_TWO_ARG | CS_INTERP, do_pemit},
    {(char *) "@function", function_sw, CA_GOD,
     0, CS_TWO_ARG | CS_INTERP, do_function},
    {(char *) "@halt", halt_sw, CA_NO_SLAVE,
     0, CS_ONE_ARG | CS_INTERP, do_halt},
    {(char *) "@kick", NULL, CA_WIZARD,
     QUEUE_KICK, CS_ONE_ARG | CS_INTERP, do_queue},
    {(char *) "@last", NULL, CA_NO_GUEST,
     0, CS_ONE_ARG | CS_INTERP, do_last},
    {(char *) "@link", NULL,
     CA_NO_SLAVE | CA_GBL_BUILD | CA_NO_GUEST,
     0, CS_TWO_ARG | CS_INTERP, do_link},
    {(char *) "@list", NULL, 0,
     0, CS_ONE_ARG | CS_INTERP, do_list},
    {(char *) "@list_file", NULL, CA_WIZARD,
     0, CS_ONE_ARG | CS_INTERP, do_list_file},
    {(char *) "@listmotd", listmotd_sw, 0,
     MOTD_LIST, CS_ONE_ARG, do_motd},
    {(char *) "@lock", lock_sw, CA_NO_SLAVE | CA_GBL_BUILD,
     0, CS_TWO_ARG | CS_INTERP, do_lock},
    {(char *) "@mark", mark_sw, CA_WIZARD,
     SRCH_MARK, CS_ONE_ARG | CS_NOINTERP, do_search},
    {(char *) "@mark_all", markall_sw, CA_WIZARD,
     MARK_SET, CS_NO_ARGS, do_markall},
    {(char *) "@motd", motd_sw, CA_WIZARD,
     0, CS_ONE_ARG, do_motd},
    {(char *) "@mudwho", NULL, CA_NO_SLAVE,
     0, CS_TWO_ARG | CS_INTERP, do_mudwho},
    {(char *) "@mvattr", NULL,
     CA_NO_SLAVE | CA_NO_GUEST | CA_GBL_BUILD,
     0, CS_TWO_ARG | CS_ARGV, do_mvattr},
    {(char *) "@name", NULL,
     CA_NO_SLAVE | CA_GBL_BUILD | CA_NO_GUEST,
     0, CS_TWO_ARG | CS_INTERP, do_name},
    {(char *) "@newpassword", NULL, CA_WIZARD,
     PASS_ANY, CS_TWO_ARG, do_newpassword},
    {(char *) "@notify", notify_sw,
     CA_GBL_INTERP | CA_NO_SLAVE | CA_NO_GUEST,
     0, CS_TWO_ARG, do_notify},
    {(char *) "@oemit", NULL,
     CA_LOCATION | CA_NO_GUEST | CA_NO_SLAVE,
     PEMIT_OEMIT, CS_TWO_ARG | CS_INTERP, do_pemit},
    {(char *) "@open", open_sw,
     CA_NO_SLAVE | CA_GBL_BUILD | CA_NO_GUEST,
     0, CS_TWO_ARG | CS_ARGV | CS_INTERP, do_open},
    {(char *) "@parent", parent_sw,
     CA_NO_SLAVE | CA_GBL_BUILD | CA_NO_GUEST,
     0, CS_TWO_ARG, do_parent},
    {(char *) "@password", NULL, CA_NO_GUEST,
     PASS_MINE, CS_TWO_ARG, do_password},
    {(char *) "@pcreate", NULL, CA_WIZARD | CA_GBL_BUILD,
     PCRE_PLAYER, CS_TWO_ARG, do_pcreate},
    {(char *) "@pemit", pemit_sw, CA_NO_GUEST | CA_NO_SLAVE,
     PEMIT_PEMIT, CS_TWO_ARG | CS_INTERP, do_pemit},
    {(char *) "@poor", NULL, CA_GOD,
     0, CS_ONE_ARG | CS_INTERP, do_poor},
    {(char *) "@ps", ps_sw, 0,
     0, CS_ONE_ARG | CS_INTERP, do_ps},
    {(char *) "@quota", quota_sw, 0,
     0, CS_TWO_ARG | CS_INTERP, do_quota},
    {(char *) "@readcache", NULL, CA_WIZARD,
     0, CS_NO_ARGS, do_readcache},
    {(char *) "@robot", NULL,
     CA_NO_SLAVE | CA_GBL_BUILD | CA_NO_GUEST | CA_PLAYER,
     PCRE_ROBOT, CS_TWO_ARG, do_pcreate},
    {(char *) "@rwho", rwho_sw, CA_GOD,
     0, CS_NO_ARGS, do_rwho},
    {(char *) "@search", NULL, 0,
     SRCH_SEARCH, CS_ONE_ARG | CS_NOINTERP, do_search},
    {(char *) "@set", NULL /* set_sw */ ,
     CA_NO_SLAVE | CA_GBL_BUILD | CA_NO_GUEST,
     0, CS_TWO_ARG, do_set},
    {(char *) "@shutdown", shutdown_sw, CA_WIZARD,
     0, CS_ONE_ARG, do_shutdown},
    {(char *) "@stats", stats_sw, 0,
     0, CS_ONE_ARG | CS_INTERP, do_stats},
    {(char *) "@sweep", sweep_sw, 0,
     0, CS_ONE_ARG, do_sweep},
    {(char *) "@switch", switch_sw, CA_GBL_INTERP,
     0, CS_TWO_ARG | CS_ARGV | CS_CMDARG | CS_NOINTERP | CS_STRIP_AROUND,
     do_switch},
    {(char *) "@teleport", NULL, CA_NO_GUEST,
     0, CS_TWO_ARG | CS_INTERP, do_teleport},
    {(char *) "@timewarp", warp_sw, CA_WIZARD,
     0, CS_ONE_ARG | CS_INTERP, do_timewarp},
    {(char *) "@toad", toad_sw, CA_WIZARD,
     0, CS_TWO_ARG | CS_INTERP, do_toad},
    {(char *) "@trigger", trig_sw, CA_GBL_INTERP,
     0, CS_TWO_ARG | CS_ARGV, do_trigger},
    {(char *) "@unlink", NULL, CA_NO_SLAVE | CA_GBL_BUILD,
     0, CS_ONE_ARG | CS_INTERP, do_unlink},
    {(char *) "@unlock", lock_sw, CA_NO_SLAVE | CA_GBL_BUILD,
     0, CS_ONE_ARG | CS_INTERP, do_unlock},
    {(char *) "@verb", NULL, CA_GBL_INTERP | CA_NO_SLAVE,
     0, CS_TWO_ARG | CS_ARGV | CS_INTERP | CS_STRIP_AROUND,
     do_verb},
    {(char *) "@wait", NULL, CA_GBL_INTERP,
     0, CS_TWO_ARG | CS_CMDARG | CS_NOINTERP | CS_STRIP_AROUND,
     do_wait},
    {(char *) "@wall", wall_sw, CA_WIZARD,
     SAY_SHOUT, CS_ONE_ARG | CS_INTERP, do_say},
    {(char *) "@wipe", NULL,
     CA_NO_SLAVE | CA_NO_GUEST | CA_GBL_BUILD,
     0, CS_ONE_ARG | CS_INTERP, do_wipe},
    {(char *) "drop", drop_sw,
     CA_NO_SLAVE | CA_CONTENTS | CA_LOCATION | CA_NO_GUEST,
     0, CS_ONE_ARG | CS_INTERP, do_drop},
    {(char *) "enter", enter_sw, CA_LOCATION,
     0, CS_ONE_ARG | CS_INTERP, do_enter},
    {(char *) "examine", examine_sw, 0,
     0, CS_ONE_ARG | CS_INTERP, do_examine},
    {(char *) "get", get_sw, CA_LOCATION | CA_NO_GUEST,
     0, CS_ONE_ARG | CS_INTERP, do_get},
    {(char *) "give", give_sw, CA_LOCATION | CA_NO_GUEST,
     0, CS_TWO_ARG | CS_INTERP, do_give},
    {(char *) "goto", goto_sw, CA_LOCATION,
     0, CS_ONE_ARG | CS_INTERP, do_move},
    {(char *) "help", NULL, 0,
     HELP_HELP, CS_ONE_ARG, do_help},
    {(char *) "inventory", NULL, 0,
     LOOK_INVENTORY, CS_NO_ARGS, do_inventory},
    {(char *) "kill", NULL, CA_NO_GUEST | CA_NO_SLAVE,
     KILL_KILL, CS_TWO_ARG | CS_INTERP, do_kill},
    {(char *) "leave", leave_sw, CA_LOCATION,
     0, CS_NO_ARGS | CS_INTERP, do_leave},
    {(char *) "look", look_sw, CA_LOCATION,
     LOOK_LOOK, CS_ONE_ARG | CS_INTERP, do_look},
    {(char *) "news", NULL, 0,
     HELP_NEWS, CS_ONE_ARG, do_help},
    {(char *) "page", NULL, CA_NO_SLAVE,
     0, CS_TWO_ARG | CS_INTERP, do_page},
    {(char *) "pose", pose_sw, CA_LOCATION | CA_NO_SLAVE,
     SAY_POSE, CS_ONE_ARG | CS_INTERP, do_say},
    {(char *) "say", NULL, CA_LOCATION | CA_NO_SLAVE,
     SAY_SAY, CS_ONE_ARG | CS_INTERP, do_say},
    {(char *) "score", NULL, 0,
     LOOK_SCORE, CS_NO_ARGS, do_score},
    {(char *) "slay", NULL, CA_WIZARD,
     KILL_SLAY, CS_TWO_ARG | CS_INTERP, do_kill},
    {(char *) "use", NULL, CA_NO_SLAVE | CA_GBL_INTERP,
     0, CS_ONE_ARG | CS_INTERP, do_use},
    {(char *) "version", NULL, 0,
     0, CS_NO_ARGS, do_version},
    {(char *) "whisper", NULL, CA_LOCATION | CA_NO_SLAVE,
     PEMIT_WHISPER, CS_TWO_ARG | CS_INTERP, do_pemit},
    {(char *) "wizhelp", NULL, CA_WIZARD,
     HELP_WIZHELP, CS_ONE_ARG, do_help},
    {(char *) "\\", NULL,
     CA_NO_GUEST | CA_LOCATION | CF_DARK | CA_NO_SLAVE,
     SAY_PREFIX, CS_ONE_ARG | CS_INTERP, do_say},
    {(char *) "#", NULL,
     CA_NO_SLAVE | CA_GBL_INTERP | CF_DARK,
     0, CS_ONE_ARG | CS_INTERP | CS_CMDARG, do_force_prefixed},
    {(char *) ":", NULL,
     CA_LOCATION | CF_DARK | CA_NO_SLAVE,
     SAY_PREFIX, CS_ONE_ARG | CS_INTERP, do_say},
    {(char *) "\"", NULL,
     CA_LOCATION | CF_DARK | CA_NO_SLAVE,
     SAY_PREFIX, CS_ONE_ARG | CS_INTERP, do_say},
    {(char *) "&", NULL,
     CA_NO_GUEST | CA_NO_SLAVE | CF_DARK,
     0, CS_TWO_ARG, do_setvattr},
    {(char *) NULL, NULL, 0,
     0, 0, NULL}};

CMDENT *prefix_cmds[256];

CMDENT *goto_cmdp;

void
NDECL(init_cmdtab)
{
    CMDENT *cp;
    ATTR *ap;
    char *p, *q;
    int anum;
    char *cbuff;

    hashinit(&mudstate.command_htab, 511);

    /* Load attribute-setting commands */

    cbuff = alloc_sbuf("init_cmdtab");
    for (ap = attr; ap->name; ap++) {
	if ((ap->flags & AF_NOCMD) == 0) {
	    p = cbuff;
	    *p++ = '@';
	    for (q = (char *) ap->name; *q; p++, q++)
		*p = ToLower(*q);
	    *p = '\0';
	    cp = (CMDENT *) XMALLOC(sizeof(CMDENT), "init_cmdtab");
	    cp->cmdname = (char *) strsave(cbuff);
	    cp->perms = CA_NO_GUEST | CA_NO_SLAVE;
	    if (ap->flags & (AF_WIZARD | AF_MDARK)) {
		cp->perms |= CA_WIZARD;
	    }
	    cp->extra = ap->number;
	    cp->callseq = CS_TWO_ARG;
	    cp->handler = do_setattr;
	    if (hashadd(cp->cmdname, (int *) cp, &mudstate.command_htab)) {
		XFREE(cp->cmdname, "init_cmdtab.2");
		XFREE(cp, "init_cmdtab.3");
	    }
	}
    }
    free_sbuf(cbuff);

    /* Load the builtin commands */

    for (cp = command_table; cp->cmdname; cp++)
	hashadd(cp->cmdname, (int *) cp, &mudstate.command_htab);

    /* Load the command prefix table.  Note - these commands can never
     * be typed in by a user because commands are lowercased before
     * the hash table is checked. The names are abbreviated to minimise
     * name checking time. */

    for (anum = 0; anum < A_USER_START; anum++)
	prefix_cmds[anum] = NULL;
    prefix_cmds['"'] = (CMDENT *) hashfind((char *) "\"",
					   &mudstate.command_htab);
    prefix_cmds[':'] = (CMDENT *) hashfind((char *) ":",
					   &mudstate.command_htab);
    prefix_cmds[';'] = (CMDENT *) hashfind((char *) ":",
					   &mudstate.command_htab);
    prefix_cmds['\\'] = (CMDENT *) hashfind((char *) "\\",
					    &mudstate.command_htab);
    prefix_cmds['#'] = (CMDENT *) hashfind((char *) "#",
					   &mudstate.command_htab);
    prefix_cmds['&'] = (CMDENT *) hashfind((char *) "&",
					   &mudstate.command_htab);
    goto_cmdp = (CMDENT *) hashfind("goto", &mudstate.command_htab);
}

/* ---------------------------------------------------------------------------
 * check_access: Check if player has access to function.
 */

int
check_access(player, mask)
    dbref player;
    int mask;
{
    int succ, fail;

    if (mask & CA_DISABLED)
	return 0;
    if (God(player) || mudstate.initializing)
	return 1;

    succ = fail = 0;
    if (mask & CA_GOD)
	fail++;
    if (mask & CA_WIZARD) {
	if (Wizard(player))
	    succ++;
	else
	    fail++;
    }
    if ((succ == 0) && (mask & CA_IMMORTAL)) {
	if (Immortal(player))
	    succ++;
	else
	    fail++;
    }
    if ((succ == 0) && (mask & CA_BUILDER)) {
	if (Builder(player))
	    succ++;
	else
	    fail++;
    }
    if ((succ == 0) && (mask & CA_ROBOT)) {
	if (Robot(player))
	    succ++;
	else
	    fail++;
    }
    if (succ > 0)
	fail = 0;
    if (fail > 0)
	return 0;

    /* Check for forbidden flags. */

    if (!Wizard(player) &&
	(((mask & CA_NO_HAVEN) && Player_haven(player)) ||
	 ((mask & CA_NO_ROBOT) && Robot(player)) ||
	 ((mask & CA_NO_SLAVE) && Slave(player)) ||
	 ((mask & CA_NO_SUSPECT) && Suspect(player)) ||
	 ((mask & CA_NO_GUEST) && Guest(player)))) {
	return 0;
    }
    return 1;
}

/* ---------------------------------------------------------------------------
 * process_cmdent: Perform indicated command with passed args.
 */

void
process_cmdent(cmdp, switchp, player, cause, interactive, arg,
	       unp_command, cargs, ncargs)
    CMDENT *cmdp;
    char *switchp, *arg, *unp_command, *cargs[];
    dbref player, cause;
    int interactive, ncargs;
{
    char *buf1, *buf2, tchar;
    char *args[MAX_ARG];
    int nargs, i, fail, interp, key, xkey;

#define	Protect(x) (cmdp->perms & x)

    /* Perform object type checks. */

    fail = 0;
    if (Protect(CA_LOCATION) && !Has_location(player))
	fail++;
    if (Protect(CA_CONTENTS) && !Has_contents(player))
	fail++;
    if (Protect(CA_PLAYER) && !isPlayer(player))
	fail++;
    if (fail > 0) {
	notify(player, "Command incompatible with invoker type.");
	return;
    }
    /* Check if we have permission to execute the command */

    if (!check_access(player, cmdp->perms)) {
	notify(player, "Permission denied.");
	return;
    }
    /* Check global flags */

    if (Protect(CA_GBL_BUILD) && !(mudconf.control_flags & CF_BUILD)) {
	notify(player, "Sorry, building is not allowed now.");
	return;
    }
    if (Protect(CA_GBL_INTERP) && !(mudconf.control_flags & CF_INTERP)) {
	notify(player,
	       "Sorry, queueing and triggering are not allowed now.");
	return;
    }
    key = cmdp->extra & ~SW_MULTIPLE;
    if (key & SW_GOT_UNIQUE) {
	i = 1;
	key = key & ~SW_GOT_UNIQUE;
    } else {
	i = 0;
    }

    /* Check command switches.  Note that there may be more than one,
     * and that we OR all of them together along with the extra value from
     * the command table to produce the key value in the handler call.
     */

    if (switchp && cmdp->switches) {
	do {
	    buf1 = (char *) index(switchp, '/');
	    if (buf1)
		*buf1++ = '\0';
	    xkey = search_nametab(player, cmdp->switches,
				  switchp);
	    if (xkey == -1) {
		notify(player,
		       tprintf("Unrecognized switch '%s' for command '%s'.",
			       switchp, cmdp->cmdname));
		return;
	    } else if (!(xkey & SW_MULTIPLE)) {
		if (i == 1) {
		    notify(player,
			   "Illegal combination of switches.");
		    return;
		}
		i = 1;
	    } else {
		xkey &= ~SW_MULTIPLE;
	    }
	    key |= xkey;
	    switchp = buf1;
	} while (buf1);
    } else if (switchp) {
	notify(player,
	       tprintf("Command %s does not take switches.",
		       cmdp->cmdname));
	return;
    }
    /* We are allowed to run the command.  Now, call the handler using the
     * appropriate calling sequence and arguments.
     */

    /* If the command normally has interpreted args, but the user
     * specified, /noeval, just do EV_STRIP.
     *
     * If the command is interpreted, or we're interactive (and
     * the command isn't specified CS_NOINTERP), eval the args.
     * 
     * The others are obvious.
     */
    if ((cmdp->callseq & CS_INTERP) && (key & SW_NOEVAL)) {
	interp = EV_STRIP;
	key &= ~SW_NOEVAL;	/* Remove SW_NOEVAL from 'key' */
    }
    else if ((cmdp->callseq & CS_INTERP) ||
	!(interactive || (cmdp->callseq & CS_NOINTERP)))
	interp = EV_EVAL | EV_STRIP;
    else if (cmdp->callseq & CS_STRIP)
	interp = EV_STRIP;
    else if (cmdp->callseq & CS_STRIP_AROUND)
	interp = EV_STRIP_AROUND;
    else
	interp = 0;

    switch (cmdp->callseq & CS_NARG_MASK) {
    case CS_NO_ARGS:		/* <cmd>   (no args) */
	(*(cmdp->handler)) (player, cause, key);
	break;
    case CS_ONE_ARG:		/* <cmd> <arg> */

	/* If an unparsed command, just give it to the handler */

	if (cmdp->callseq & CS_UNPARSE) {
	    (*(cmdp->handler)) (player, unp_command);
	    break;
	}
	/* Interpret if necessary */

	if (interp & EV_EVAL)
	    buf1 = exec(player, cause, interp | EV_FCHECK | EV_TOP,
			arg, cargs, ncargs);
	else
	    buf1 = parse_to(&arg, '\0', interp | EV_TOP);

	/* Call the correct handler */

	if (cmdp->callseq & CS_CMDARG) {
	    (*(cmdp->handler)) (player, cause, key, buf1,
				cargs, ncargs);
	} else {
	    (*(cmdp->handler)) (player, cause, key, buf1);
	}

	/* Free the buffer if one was allocated */

	if (interp & EV_EVAL)
	    free_lbuf(buf1);
	break;
    case CS_TWO_ARG:		/* <cmd> <arg1> = <arg2> */

	/* Interpret ARG1 */

	buf2 = parse_to(&arg, '=', EV_STRIP_TS);

	/* Handle when no '=' was specified */

	if (!arg || (arg && !*arg)) {
	    arg = &tchar;
	    *arg = '\0';
	}
	buf1 = exec(player, cause, EV_STRIP | EV_FCHECK | EV_EVAL | EV_TOP,
		    buf2, cargs, ncargs);

	if (cmdp->callseq & CS_ARGV) {

	    /* Arg2 is ARGV style.  Go get the args */

	    parse_arglist(player, cause, arg, '\0',
			  interp | EV_STRIP_LS | EV_STRIP_TS,
			  args, MAX_ARG, cargs, ncargs);
	    for (nargs = 0; (nargs < MAX_ARG) && args[nargs]; nargs++);

	    /* Call the correct command handler */

	    if (cmdp->callseq & CS_CMDARG) {
		(*(cmdp->handler)) (player, cause, key,
				    buf1, args, nargs, cargs, ncargs);
	    } else {
		(*(cmdp->handler)) (player, cause, key,
				    buf1, args, nargs);
	    }

	    /* Free the argument buffers */

	    for (i = 0; i <= nargs; i++)
		if (args[i])
		    free_lbuf(args[i]);
	} else {

	    /* Arg2 is normal style.  Interpret if needed */

	    if (interp & EV_EVAL) {
		buf2 = exec(player, cause,
			    interp | EV_FCHECK | EV_TOP,
			    arg, cargs, ncargs);
	    } else {
		buf2 = parse_to(&arg, '\0',
				interp | EV_STRIP_LS | EV_STRIP_TS | EV_TOP);
	    }

	    /* Call the correct command handler */

	    if (cmdp->callseq & CS_CMDARG) {
		(*(cmdp->handler)) (player, cause, key,
				    buf1, buf2, cargs, ncargs);
	    } else {
		(*(cmdp->handler)) (player, cause, key,
				    buf1, buf2);
	    }

	    /* Free the buffer, if needed */

	    if (interp & EV_EVAL)
		free_lbuf(buf2);
	}

	/* Free the buffer obtained by evaluating Arg1 */

	free_lbuf(buf1);
	break;
    }
    return;
}

/* ---------------------------------------------------------------------------
 * process_command: Execute a command.
 */

char *
process_command(player, cause, interactive, command, args, nargs)
    dbref player, cause;
    int interactive, nargs;
    char *command, *args[];
{
    static char preserve_cmd[LBUF_SIZE];
    char *p, *q, *arg, *lcbuf, *slashp, *cmdsave;
    int succ, aflags, i, got_stop;
    dbref exit, aowner, parent;
    CMDENT *cmdp;

    cache_reset(0);

    /* Robustify player */

    cmdsave = mudstate.debug_cmd;
    mudstate.debug_cmd = (char *) "< process_command >";

    if (!command)
	abort();

    if (!Good_obj(player)) {
	STARTLOG(LOG_BUGS, "CMD", "PLYR")
	    lcbuf = alloc_mbuf("process_command.LOG.badplayer");
	sprintf(lcbuf, "Bad player in process_command: %d",
		player);
	log_text(lcbuf);
	free_mbuf(lcbuf);
	ENDLOG
	    mudstate.debug_cmd = cmdsave;
	return command;
    }
    /* Make sure player isn't going or halted */

    if (Going(player) ||
	(Halted(player) &&
	 !(isPlayer(player) && interactive))) {
	notify(Owner(player),
	       tprintf("Attempt to execute command by halted object #%d",
		       player));
	mudstate.debug_cmd = cmdsave;
	return command;
    }
    STARTLOG(LOG_ALLCOMMANDS, "CMD", "ALL")
	log_name_and_loc(player);
    lcbuf = alloc_lbuf("process_command.LOG.allcmds");
    sprintf(lcbuf, " entered: '%s'", command);
    log_text(lcbuf);
    free_lbuf(lcbuf);
    ENDLOG

    /* Reset recursion limits */

    mudstate.func_nest_lev = 0;
    mudstate.func_invk_ctr = 0;
    mudstate.ntfy_nest_lev = 0;
    mudstate.lock_nest_lev = 0;

    if (Verbose(player))
	notify(Owner(player), tprintf("%s] %s", Name(player),
				      command));

    /*
     * NOTE THAT THIS WILL BREAK IF "GOD" IS NOT A DBREF.
     */
    if (mudconf.control_flags & CF_GODMONITOR) {
        raw_notify(GOD, tprintf("%s(#%d)%c %s", Name(player), player,
				(interactive) ? '|' : ':', command));
    }

    /* Eat leading whitespace, and space-compress if configured */

    while (*command && isspace(*command))
	command++;

    strcpy(preserve_cmd, command);
    mudstate.curr_cmd = preserve_cmd;
    mudstate.debug_cmd = command;

    if (mudconf.space_compress) {
	p = q = command;
	while (*p) {
	    while (*p && !isspace(*p))
		*q++ = *p++;	/* scan over word */
	    while (*p && isspace(*p))
		p++;		/* smash spaces */
	    if (*p)
		*q++ = ' ';	/* separate words */
	}
	*q = '\0';		/* terminate string */
    }
    /* Reset the cache so that unreferenced attributes may be flushed */

    cache_reset();

    /* Now comes the fun stuff.  First check for single-letter leadins.
     * We check these before checking HOME because they are among the most
     * frequently executed commands, and they can never be the HOME
     * command. */

    i = command[0] & 0xff;
    if ((prefix_cmds[i] != NULL) && command[0]) {
	process_cmdent(prefix_cmds[i], NULL, player, cause,
		       interactive, command, command, args, nargs);
	mudstate.debug_cmd = cmdsave;
	return preserve_cmd;
    }
    /* Check for the HOME command */

    if (string_compare(command, "home") == 0) {
	do_move(player, cause, 0, "home");
	mudstate.debug_cmd = cmdsave;
	return preserve_cmd;
    }
    /* Only check for exits if we may use the goto command */

    if (check_access(player, goto_cmdp->perms)) {

	/* Check for an exit name */

	init_match_check_keys(player, command, TYPE_EXIT);
	match_exit_with_parents();
	exit = last_match_result();
	if (exit != NOTHING) {
	    move_exit(player, exit, 0, "You can't go that way.", 0);
	    mudstate.debug_cmd = cmdsave;
	    return preserve_cmd;
	}
	/* Check for an exit in the master room */

	init_match_check_keys(player, command, TYPE_EXIT);
	match_master_exit();
	exit = last_match_result();
	if (exit != NOTHING) {
	    move_exit(player, exit, 1, NULL, 0);
	    mudstate.debug_cmd = cmdsave;
	    return preserve_cmd;
	}
    }
    /* Set up a lowercase command and an arg pointer for the hashed
     * command check.  Since some types of argument processing destroy
     * the arguments, make a copy so that we keep the original command
     * line intact.  Store the edible copy in lcbuf after the lowercased
     * command. */
    /* Removed copy of the rest of the command, since it's ok to allow
     * it to be trashed.  -dcm */

    lcbuf = alloc_lbuf("process_commands.LCbuf");
    for (p = command, q = lcbuf; *p && !isspace(*p); p++, q++)
	*q = ToLower(*p);	/* Make lowercase command */
    *q++ = '\0';		/* Terminate command */
    while (*p && isspace(*p))
	p++;			/* Skip spaces before arg */
    arg = p;			/* Remember where arg starts */

    /* Strip off any command switches and save them */

    slashp = (char *) index(lcbuf, '/');
    if (slashp)
	*slashp++ = '\0';

    /* Check for a builtin command (or an alias of a builtin command) */

    cmdp = (CMDENT *) hashfind(lcbuf, &mudstate.command_htab);
    if (cmdp != NULL) {
	process_cmdent(cmdp, slashp, player, cause, interactive, arg,
		       command, args, nargs);
	free_lbuf(lcbuf);
	mudstate.debug_cmd = cmdsave;
	return preserve_cmd;
    }

    /*
     * Check for enter and leave aliases, user-defined commands on the
     * player, other objects where the player is, on objects in the
     * player's inventory, and on the room that holds the player.  We
     * evaluate the command line here to allow chains of $-commands to
     * work.
     */

    free_lbuf(lcbuf);
    lcbuf = exec(player, cause, EV_EVAL | EV_FCHECK | EV_STRIP | EV_TOP, 
		 command, args, nargs);
    succ = 0;

    /* Idea for enter/leave aliases from R'nice@TinyTIM */

    if (Has_location(player) && Good_obj(Location(player))) {

	/* Check for a leave alias */

	p = atr_pget(Location(player), A_LALIAS, &aowner, &aflags);
	if (p && *p) {
	    if (matches_exit_from_list(lcbuf, p)) {
		free_lbuf(lcbuf);
		free_lbuf(p);
		do_leave(player, player, 0);
		return preserve_cmd;
	    }
	}
	free_lbuf(p);

	/* Check for enter aliases */

	DOLIST(exit, Contents(Location(player))) {
	    p = atr_pget(exit, A_EALIAS, &aowner, &aflags);
	    if (p && *p) {
		if (matches_exit_from_list(lcbuf, p)) {
		    free_lbuf(lcbuf);
		    free_lbuf(p);
		    do_enter_internal(player, exit, 0);
		    return preserve_cmd;
		}
	    }
	    free_lbuf(p);
	}
    }

    /* At each of the following stages, we check to make sure that we 
     * haven't hit a match on a STOP-set object.
     */

    got_stop = 0;

    /* Check for $-command matches on me */

    if (mudconf.match_mine) {
	if ((!isPlayer(player) ||
	     mudconf.match_mine_pl) &&
	    (atr_match(player, player, AMATCH_CMD, lcbuf, preserve_cmd, 1) > 0)) {
	    succ++;
	    got_stop = Stop_Match(player);
	}
    }

    /* Check for $-command matches on nearby things and on my room. */

    if (!got_stop && Has_location(player)) {
	succ += list_check(Contents(Location(player)), player,
			   AMATCH_CMD, lcbuf, preserve_cmd, 1, &got_stop);
	if (!got_stop &&
	    atr_match(Location(player), player, AMATCH_CMD, lcbuf, 
		      preserve_cmd, 1) > 0) {
	    succ++;
	    got_stop = Stop_Match(Location(player));
	}
    }

    /* Check for $-command matches in my inventory */

    if (!got_stop && Has_contents(player))
	succ += list_check(Contents(player), player,
			   AMATCH_CMD, lcbuf, preserve_cmd, 1, &got_stop);

    /* If we didn't find anything, and we're checking local masters, 
     * do those checks. Do it for the zone of the player's location first, 
     * and then, if nothing is found, on the player's personal zone.
     * Walking back through the parent tree stops when a match is found.
     * Also note that these matches are done in the style of the master room:
     * parents of the contents of the rooms aren't checked for commands.
     */

    if (!succ && mudconf.local_masters) {
	
	if (Has_location(player)) {
	    parent = Parent(Location(player));
	    while (!succ && !got_stop && Good_obj(parent) && Zone(parent)) {
		if (Has_contents(parent)) {
		    succ += list_check(Contents(parent), player, AMATCH_CMD,
				       lcbuf, preserve_cmd, 0, &got_stop);
		}
		parent = Parent(parent);
	    }
	    /* Here's a hack: we check the secondary "zone" parent, which
	     * is used ONLY for this particular sort of match.
	     */
	    if (mudconf.parent_zones &&
		(Typeof(Location(player)) == TYPE_ROOM)) {
		parent = Next(Location(player));
		while (!succ && !got_stop && 
		       Good_obj(parent) && Zone(parent)) {
		    if (Has_contents(parent)) {
			succ += list_check(Contents(parent), player, 
					   AMATCH_CMD, lcbuf, preserve_cmd, 0, 
					   &got_stop);
		    }
		    parent = Parent(parent);
		}
	    }
	}

	if (!succ) {
	    parent = Parent(player);
	    if ((parent != Location(player)) &&
		(parent != Parent(Location(player)))) {
		while (!succ && !got_stop && 
		       Good_obj(parent) && Zone(parent)) {
		    if (Has_contents(parent)) {
			succ += list_check(Contents(parent), player,
					   AMATCH_CMD, lcbuf, preserve_cmd, 0,
					   &got_stop);
		    }
		    parent = Parent(parent);
		}
	    }
	}
    }

    /* If we didn't find anything, try in the master room */

    if (!succ) {
	if (Good_obj(mudconf.master_room) &&
	    Has_contents(mudconf.master_room)) {
	    succ += list_check(Contents(mudconf.master_room),
			       player, AMATCH_CMD, lcbuf, preserve_cmd, 0,
			       &got_stop);
	    if (!got_stop && atr_match(mudconf.master_room,
			  player, AMATCH_CMD, lcbuf, preserve_cmd, 0) > 0) {
		succ++;
	    }
	}
    }
    free_lbuf(lcbuf);

    /* If we still didn't find anything, tell how to get help. */

    if (!succ) {
	notify(player, "Huh?  (Type \"help\" for help.)");
	STARTLOG(LOG_BADCOMMANDS, "CMD", "BAD")
	    log_name_and_loc(player);
	lcbuf = alloc_lbuf("process_commands.LOG.badcmd");
	sprintf(lcbuf, " entered: '%s'", preserve_cmd);
	log_text(lcbuf);
	free_lbuf(lcbuf);
	ENDLOG
    }
    mudstate.debug_cmd = cmdsave;
    return preserve_cmd;
}

/* ---------------------------------------------------------------------------
 * powers_nametab: Extended powers table.
 * IMPORTANT - Keep this table in sync with the POW_xxx #defines in externs.h
 */

NAMETAB powers_nametab[] =
{
    {(char *) "change_quotas", 3, CA_WIZARD, CA_WIZARD},
    {(char *) "chown_anywhere", 6, CA_WIZARD, CA_GOD},
    {(char *) "chown_mine", 7, CA_WIZARD, CA_WIZARD},
    {(char *) "chown_others", 7, CA_WIZARD, CA_WIZARD},
    {(char *) "chown_players", 7, CA_WIZARD, CA_GOD},
    {(char *) "control_global", 2, CA_WIZARD, CA_WIZARD},
    {(char *) "expanded_who", 3, CA_WIZARD, CA_WIZARD},
    {(char *) "examine_global", 3, CA_WIZARD, CA_WIZARD | CA_IMMORTAL},
    {(char *) "find_unfindable", 2, CA_WIZARD, CA_WIZARD | CA_IMMORTAL},
    {(char *) "free_money", 6, CA_WIZARD, CA_WIZARD | CA_IMMORTAL},
    {(char *) "free_quota", 6, CA_WIZARD, CA_WIZARD | CA_IMMORTAL},
    {(char *) "grab_player", 1, CA_WIZARD, CA_WIZARD},
    {(char *) "ignore_rst_flags", 1, CA_WIZARD, CA_WIZARD},
    {(char *) "join_player", 1, CA_WIZARD, CA_WIZARD},
    {(char *) "long_fingers", 1, CA_WIZARD, CA_WIZARD | CA_IMMORTAL},
    {(char *) "no_boot", 4, CA_WIZARD, CA_GOD},
    {(char *) "no_toad", 4, CA_WIZARD, CA_WIZARD},
    {(char *) "rename_myself", 1, CA_WIZARD, CA_NO_GUEST | CA_NO_SLAVE},
    {(char *) "see_admin_flags", 6, CA_WIZARD, CA_WIZARD},
    {(char *) "see_all_queue", 6, CA_WIZARD, CA_WIZARD},
    {(char *) "see_hidden", 5, CA_WIZARD, CA_WIZARD | CA_IMMORTAL},
    {(char *) "see_inviz_attrs", 5, CA_WIZARD, CA_WIZARD},
    {(char *) "see_maint_flags", 5, CA_WIZARD, CA_GOD},
    {(char *) "set_admin_attrs", 11, CA_WIZARD, CA_WIZARD},
    {(char *) "set_admin_flags", 11, CA_WIZARD, CA_WIZARD},
    {(char *) "set_maint_attrs", 11, CA_WIZARD, CA_WIZARD},
    {(char *) "set_maint_flags", 11, CA_WIZARD, CA_WIZARD},
    {(char *) "stat_any", 3, CA_WIZARD, CA_WIZARD},
    {(char *) "steal_money", 3, CA_WIZARD, CA_WIZARD},
    {(char *) "tel_anywhere", 6, CA_WIZARD, CA_WIZARD},
    {(char *) "tel_unrestricted", 6, CA_WIZARD, CA_GOD},
    {(char *) "unkillable", 1, CA_WIZARD, CA_WIZARD | CA_IMMORTAL},
    {NULL, 0, 0, 0}};

/* ---------------------------------------------------------------------------
 * list_cmdtable: List internal commands.
 */

static void
list_cmdtable(player)
    dbref player;
{
    CMDENT *cmdp;
    char *buf, *bp, *cp;

    buf = alloc_lbuf("list_cmdtable");
    bp = buf;
    for (cp = (char *) "Commands:"; *cp; cp++)
	*bp++ = *cp;
    for (cmdp = command_table; cmdp->cmdname; cmdp++) {
	if (check_access(player, cmdp->perms)) {
	    if (!(cmdp->perms & CF_DARK)) {
		*bp++ = ' ';
		for (cp = cmdp->cmdname; *cp; cp++)
		    *bp++ = *cp;
	    }
	}
    }
    *bp = '\0';

    /* Players get the list of logged-out cmds too */

    if (isPlayer(player))
	display_nametab(player, logout_cmdtable, buf, 1);
    else
	notify(player, buf);
    free_lbuf(buf);
}

/* ---------------------------------------------------------------------------
 * list_attrtable: List available attributes.
 */

static void
list_attrtable(player)
    dbref player;
{
    ATTR *ap;
    char *buf, *bp, *cp;

    buf = alloc_lbuf("list_attrtable");
    bp = buf;
    for (cp = (char *) "Attributes:"; *cp; cp++)
	*bp++ = *cp;
    for (ap = attr; ap->name; ap++) {
	if (See_attr(player, player, ap, player, 0)) {
	    *bp++ = ' ';
	    for (cp = (char *) (ap->name); *cp; cp++)
		*bp++ = *cp;
	}
    }
    *bp = '\0';
    notify(player, buf);
    free_lbuf(buf);
}

/* ---------------------------------------------------------------------------
 * list_ntab_flags: List flags field of an ntab.
 */

static void
list_ntab_flags(player, ntab, flaglist)
    dbref player;
    NAMETAB *ntab, *flaglist;
{
    char *buff;
    NAMETAB *np;

    buff = alloc_sbuf("list_attraccess");
    for (np = ntab; np->name; np++) {
	if (check_access(player, np->perm)) {
	    sprintf(buff, "%s:", np->name);
	    listset_nametab(player, flaglist,
			    np->flag, buff, 1);
	}
    }
    free_sbuf(buff);
}

/* ---------------------------------------------------------------------------
 * list_cmdaccess: List access commands.
 */

NAMETAB access_nametab[] =
{
    {(char *) "god", 2, CA_GOD, CA_GOD},
    {(char *) "wizard", 3, CA_WIZARD, CA_WIZARD},
    {(char *) "builder", 6, CA_WIZARD, CA_BUILDER},
    {(char *) "immortal", 3, CA_WIZARD, CA_IMMORTAL},
    {(char *) "robot", 2, CA_WIZARD, CA_ROBOT},
    {(char *) "no_haven", 4, CA_PUBLIC, CA_NO_HAVEN},
    {(char *) "no_robot", 4, CA_WIZARD, CA_NO_ROBOT},
    {(char *) "no_slave", 5, CA_PUBLIC, CA_NO_SLAVE},
    {(char *) "no_suspect", 5, CA_WIZARD, CA_NO_SUSPECT},
    {(char *) "no_guest", 5, CA_WIZARD, CA_NO_GUEST},
    {(char *) "global_build", 8, CA_PUBLIC, CA_GBL_BUILD},
    {(char *) "global_interp", 8, CA_PUBLIC, CA_GBL_INTERP},
    {(char *) "disabled", 4, CA_GOD, CA_DISABLED},
    {(char *) "need_location", 6, CA_PUBLIC, CA_LOCATION},
    {(char *) "need_contents", 6, CA_PUBLIC, CA_CONTENTS},
    {(char *) "need_player", 6, CA_PUBLIC, CA_PLAYER},
    {(char *) "dark", 4, CA_GOD, CF_DARK},
    {NULL, 0, 0, 0}};

static void
list_cmdaccess(player)
    dbref player;
{
    char *buff, *p, *q;
    CMDENT *cmdp;
    ATTR *ap;

    buff = alloc_sbuf("list_cmdaccess");
    for (cmdp = command_table; cmdp->cmdname; cmdp++) {
	if (check_access(player, cmdp->perms)) {
	    if (!(cmdp->perms & CF_DARK)) {
		sprintf(buff, "%s:", cmdp->cmdname);
		listset_nametab(player, access_nametab,
				cmdp->perms, buff, 1);
	    }
	}
    }
    for (ap = attr; ap->name; ap++) {
	p = buff;
	*p++ = '@';
	for (q = (char *) ap->name; *q; p++, q++)
	    *p = ToLower(*q);
	if (ap->flags & AF_NOCMD)
	    continue;
	*p = '\0';
	cmdp = (CMDENT *) hashfind(buff, &mudstate.command_htab);
	if (cmdp == NULL)
	    continue;
	if (!check_access(player, cmdp->perms))
	    continue;
	if (!(cmdp->perms & CF_DARK)) {
	    sprintf(buff, "%s:", cmdp->cmdname);
	    listset_nametab(player, access_nametab,
			    cmdp->perms, buff, 1);
	}
    }
    free_sbuf(buff);
}

/* ---------------------------------------------------------------------------
 * list_cmdswitches: List switches for commands.
 */

static void
list_cmdswitches(player)
    dbref player;
{
    char *buff;
    CMDENT *cmdp;

    buff = alloc_sbuf("list_cmdswitches");
    for (cmdp = command_table; cmdp->cmdname; cmdp++) {
	if (cmdp->switches) {
	    if (check_access(player, cmdp->perms)) {
		if (!(cmdp->perms & CF_DARK)) {
		    sprintf(buff, "%s:", cmdp->cmdname);
		    display_nametab(player, cmdp->switches,
				    buff, 0);
		}
	    }
	}
    }
    free_sbuf(buff);
}

/* ---------------------------------------------------------------------------
 * list_attraccess: List access to attributes.
 */

NAMETAB attraccess_nametab[] =
{
    {(char *) "dark", 2, CA_WIZARD, AF_DARK},
    {(char *) "deleted", 2, CA_WIZARD, AF_DELETED},
    {(char *) "god", 1, CA_PUBLIC, AF_GOD},
    {(char *) "hidden", 2, CA_WIZARD, AF_MDARK},
    {(char *) "html", 2, CA_PUBLIC, AF_HTML},
    {(char *) "ignore", 2, CA_WIZARD, AF_NOCMD},
    {(char *) "internal", 2, CA_WIZARD, AF_INTERNAL},
    {(char *) "is_lock", 4, CA_PUBLIC, AF_IS_LOCK},
    {(char *) "locked", 1, CA_PUBLIC, AF_LOCK},
    {(char *) "no_command", 4, CA_PUBLIC, AF_NOPROG},
    {(char *) "no_inherit", 4, CA_PUBLIC, AF_PRIVATE},
    {(char *) "no_parse", 4, CA_PUBLIC, AF_NOPARSE},
    {(char *) "private", 1, CA_PUBLIC, AF_ODARK},
    {(char *) "visual", 1, CA_PUBLIC, AF_VISUAL},
    {(char *) "wizard", 1, CA_PUBLIC, AF_WIZARD},
    {NULL, 0, 0, 0}};

NAMETAB indiv_attraccess_nametab[] =
{
    {(char *) "hidden", 1, CA_WIZARD, AF_MDARK},
    {(char *) "html", 2, CA_PUBLIC, AF_HTML},
    {(char *) "no_command", 4, CA_PUBLIC, AF_NOPROG},
    {(char *) "no_inherit", 4, CA_PUBLIC, AF_PRIVATE},
    {(char *) "no_parse", 4, CA_PUBLIC, AF_NOPARSE},
    {(char *) "visual", 1, CA_PUBLIC, AF_VISUAL},
    {(char *) "wizard", 1, CA_WIZARD, AF_WIZARD},
    {NULL, 0, 0, 0}};

static void
list_attraccess(player)
    dbref player;
{
    char *buff;
    ATTR *ap;

    buff = alloc_sbuf("list_attraccess");
    for (ap = attr; ap->name; ap++) {
	if (Read_attr(player, player, ap, player, 0)) {
	    sprintf(buff, "%s:", ap->name);
	    listset_nametab(player, attraccess_nametab,
			    ap->flags, buff, 1);
	}
    }
    free_sbuf(buff);
}

/* ---------------------------------------------------------------------------
 * cf_access: Change command or switch permissions.
 */

extern CF_HDCL(cf_modify_bits);
extern void FDECL(cf_log_notfound, (dbref, char *, const char *, char *));

CF_HAND(cf_access)
{
    CMDENT *cmdp;
    char *ap;
    int set_switch;

    for (ap = str; *ap && !isspace(*ap) && (*ap != '/'); ap++);
    if (*ap == '/') {
	set_switch = 1;
	*ap++ = '\0';
    } else {
	set_switch = 0;
	if (*ap)
	    *ap++ = '\0';
	while (*ap && isspace(*ap))
	    ap++;
    }

    cmdp = (CMDENT *) hashfind(str, &mudstate.command_htab);
    if (cmdp != NULL) {
	if (set_switch)
	    return cf_ntab_access((int *) cmdp->switches, ap,
				  extra, player, cmd);
	else
	    return cf_modify_bits(&(cmdp->perms), ap,
				  extra, player, cmd);
    } else {
	cf_log_notfound(player, cmd, "Command", str);
	return -1;
    }
}

/* ---------------------------------------------------------------------------
 * cf_acmd_access: Chante command permissions for all attr-setting cmds.
 */

CF_HAND(cf_acmd_access)
{
    CMDENT *cmdp;
    ATTR *ap;
    char *buff, *p, *q;
    int failure, save;

    buff = alloc_sbuf("cf_acmd_access");
    for (ap = attr; ap->name; ap++) {
	p = buff;
	*p++ = '@';
	for (q = (char *) ap->name; *q; p++, q++)
	    *p = ToLower(*q);
	*p = '\0';
	cmdp = (CMDENT *) hashfind(buff, &mudstate.command_htab);
	if (cmdp != NULL) {
	    save = cmdp->perms;
	    failure = cf_modify_bits(&(cmdp->perms), str,
				     extra, player, cmd);
	    if (failure != 0) {
		cmdp->perms = save;
		free_sbuf(buff);
		return -1;
	    }
	}
    }
    free_sbuf(buff);
    return 0;
}

/* ---------------------------------------------------------------------------
 * cf_attr_access: Change access on an attribute.
 */

CF_HAND(cf_attr_access)
{
    ATTR *ap;
    char *sp;

    for (sp = str; *sp && !isspace(*sp); sp++);
    if (*sp)
	*sp++ = '\0';
    while (*sp && isspace(*sp))
	sp++;

    ap = atr_str(str);
    if (ap != NULL)
	return cf_modify_bits(&(ap->flags), sp, extra, player, cmd);
    else {
	cf_log_notfound(player, cmd, "Attribute", str);
	return -1;
    }
}

/* ---------------------------------------------------------------------------
 * cf_cmd_alias: Add a command alias.
 */

CF_HAND(cf_cmd_alias)
{
    char *alias, *orig, *ap;
    CMDENT *cmdp, *cmd2;
    NAMETAB *nt;
    int *hp;

    alias = strtok(str, " \t=,");
    orig = strtok(NULL, " \t=,");

    if (!orig)			/* we only got one argument to @alias.  Bad. */
	return -1;

    for (ap = orig; *ap && (*ap != '/'); ap++);
    if (*ap == '/') {

	/* Switch form of command aliasing: create an alias for a
	 * command + a switch */

	*ap++ = '\0';

	/* Look up the command */

	cmdp = (CMDENT *) hashfind(orig, (HASHTAB *) vp);
	if (cmdp == NULL) {
	    cf_log_notfound(player, cmd, "Command", orig);
	    return -1;
	}
	/* Look up the switch */

	nt = find_nametab_ent(player, (NAMETAB *) cmdp->switches, ap);
	if (!nt) {
	    cf_log_notfound(player, cmd, "Switch", ap);
	    return -1;
	}
	/* Got it, create the new command table entry */

	cmd2 = (CMDENT *) XMALLOC(sizeof(CMDENT), "cf_cmd_alias");
	cmd2->cmdname = strsave(alias);
	cmd2->switches = cmdp->switches;
	cmd2->perms = cmdp->perms | nt->perm;
	cmd2->extra = (cmdp->extra | nt->flag) & ~SW_MULTIPLE;
	if (!(nt->flag & SW_MULTIPLE))
	    cmd2->extra |= SW_GOT_UNIQUE;
	cmd2->callseq = cmdp->callseq;
	cmd2->handler = cmdp->handler;
	if (hashadd(cmd2->cmdname, (int *) cmd2, (HASHTAB *) vp)) {
	    XFREE(cmd2->cmdname, "cf_cmd_alias.2");
	    XFREE(cmd2, "cf_cmd_alias.3");
	}
    } else {

	/* A normal (non-switch) alias */

	hp = hashfind(orig, (HASHTAB *) vp);
	if (hp == NULL) {
	    cf_log_notfound(player, cmd, "Entry", orig);
	    return -1;
	}
	hashadd(alias, hp, (HASHTAB *) vp);
    }
    return 0;
}

/* ---------------------------------------------------------------------------
 * list_df_flags: List default flags at create time.
 */

static void
list_df_flags(player)
    dbref player;
{
    char *playerb, *roomb, *thingb, *exitb, *robotb, *buff;

    playerb = decode_flags(player,
			   (mudconf.player_flags.word1 | TYPE_PLAYER),
			   mudconf.player_flags.word2);
    roomb = decode_flags(player,
			 (mudconf.room_flags.word1 | TYPE_ROOM),
			 mudconf.room_flags.word2);
    exitb = decode_flags(player,
			 (mudconf.exit_flags.word1 | TYPE_EXIT),
			 mudconf.exit_flags.word2);
    thingb = decode_flags(player,
			  (mudconf.thing_flags.word1 | TYPE_THING),
			  mudconf.thing_flags.word2);
    robotb = decode_flags(player,
			  (mudconf.robot_flags.word1 | TYPE_PLAYER),
			  mudconf.robot_flags.word2);
    buff = alloc_lbuf("list_df_flags");
    sprintf(buff,
	    "Default flags: Players...%s Rooms...%s Exits...%s Things...%s Robots...%s",
	    playerb, roomb, exitb, thingb, robotb);
    notify(player, buff);
    free_lbuf(buff);
    free_sbuf(playerb);
    free_sbuf(roomb);
    free_sbuf(exitb);
    free_sbuf(thingb);
    free_sbuf(robotb);
}

/* ---------------------------------------------------------------------------
 * list_costs: List the costs of things.
 */

#define coin_name(s)	(((s)==1) ? mudconf.one_coin : mudconf.many_coins)

static void
list_costs(player)
    dbref player;
{
    char *buff;

    buff = alloc_mbuf("list_costs");
    *buff = '\0';
    if (mudconf.quotas)
	sprintf(buff, " and %d quota", mudconf.room_quota);
    notify(player,
	   tprintf("Digging a room costs %d %s%s.",
		   mudconf.digcost, coin_name(mudconf.digcost), buff));
    if (mudconf.quotas)
	sprintf(buff, " and %d quota", mudconf.exit_quota);
    notify(player,
	   tprintf("Opening a new exit costs %d %s%s.",
		   mudconf.opencost, coin_name(mudconf.opencost), buff));
    notify(player,
	   tprintf("Linking an exit, home, or dropto costs %d %s.",
		   mudconf.linkcost, coin_name(mudconf.linkcost)));
    if (mudconf.quotas)
	sprintf(buff, " and %d quota", mudconf.thing_quota);
    if (mudconf.createmin == mudconf.createmax)
	notify(player,
	       tprintf("Creating a new thing costs %d %s%s.",
		       mudconf.createmin,
		       coin_name(mudconf.createmin), buff));
    else
	notify(player,
	       tprintf("Creating a new thing costs between %d and %d %s%s.",
		       mudconf.createmin, mudconf.createmax,
		       mudconf.many_coins, buff));
    if (mudconf.quotas)
	sprintf(buff, " and %d quota", mudconf.player_quota);
    notify(player,
	   tprintf("Creating a robot costs %d %s%s.",
		   mudconf.robotcost, coin_name(mudconf.robotcost),
		   buff));
    if (mudconf.killmin == mudconf.killmax) {
	notify(player,
	       tprintf("Killing costs %d %s, with a %d%% chance of success.",
		       mudconf.killmin, coin_name(mudconf.digcost),
		       (mudconf.killmin * 100) /
		       mudconf.killguarantee));
    } else {
	notify(player,
	       tprintf("Killing costs between %d and %d %s.",
		       mudconf.killmin, mudconf.killmax,
		       mudconf.many_coins));
	notify(player,
	       tprintf("You must spend %d %s to guarantee success.",
		       mudconf.killguarantee,
		       coin_name(mudconf.killguarantee)));
    }
    notify(player,
	   tprintf("Computationally expensive commands and functions (ie: @entrances, @find, @search, @stats (with an argument or switch), search(), and stats()) cost %d %s.",
		   mudconf.searchcost, coin_name(mudconf.searchcost)));
    if (mudconf.machinecost > 0)
	notify(player,
	       tprintf("Each command run from the queue costs 1/%d %s.",
		       mudconf.machinecost, mudconf.one_coin));
    if (mudconf.waitcost > 0) {
	notify(player,
	       tprintf("A %d %s deposit is charged for putting a command on the queue.",
		       mudconf.waitcost, mudconf.one_coin));
	notify(player, "The deposit is refunded when the command is run or canceled.");
    }
    if (mudconf.sacfactor == 0)
	ltos(buff, mudconf.sacadjust);
    else if (mudconf.sacfactor == 1) {
	if (mudconf.sacadjust < 0)
	    sprintf(buff, "<create cost> - %d", -mudconf.sacadjust);
	else if (mudconf.sacadjust > 0)
	    sprintf(buff, "<create cost> + %d", mudconf.sacadjust);
	else
	    sprintf(buff, "<create cost>");
    } else {
	if (mudconf.sacadjust < 0)
	    sprintf(buff, "(<create cost> / %d) - %d",
		    mudconf.sacfactor, -mudconf.sacadjust);
	else if (mudconf.sacadjust > 0)
	    sprintf(buff, "(<create cost> / %d) + %d",
		    mudconf.sacfactor, mudconf.sacadjust);
	else
	    sprintf(buff, "<create cost> / %d", mudconf.sacfactor);
    }
    notify(player, tprintf("The value of an object is %s.", buff));
    if (mudconf.clone_copy_cost)
	notify(player, "The default value of cloned objects is the value of the original object.");
    else
	notify(player,
	       tprintf("The default value of cloned objects is %d %s.",
		       mudconf.createmin,
		       coin_name(mudconf.createmin)));

    free_mbuf(buff);
}

/* ---------------------------------------------------------------------------
 * list_options: List more game options from mudconf.
 */

static const char *switchd[] =
{"/first", "/all"};
static const char *examd[] =
{"/brief", "/full"};
static const char *ed[] =
{"Disabled", "Enabled"};
static const char *ud[] =
{"Down", "Up"};

static void
list_options(player)
    dbref player;
{
    char *buff;
    time_t now;
    int i;

    now = time(NULL);

    /* -- Configuration Options related to Building -- */

    if (mudconf.quotas)
	notify(player, "Building quotas are enforced.");
    else
	notify(player, "Building quotas are not enforced.");
    notify(player, tprintf("There is a limit of %d objects in the database.",
			   mudconf.building_limit));
    if (mudconf.typed_quotas) 
	notify(player, "Quotas are managed by object type.");

    if (mudconf.name_spaces)
	notify(player, "Player names may contain spaces.");
    else
	notify(player, "Player names may not contain spaces.");

    /* -- Configuration Options related to Programming -- */

    notify(player,
    tprintf("Players may have at most %d commands in the queue at one time.",
	    mudconf.queuemax));
    if (mudconf.req_cmds_flag)
	notify(player, "Objects are only searched for $-commands if set COMMANDS.");
    if (mudconf.match_mine) {
	if (mudconf.match_mine_pl)
	    notify(player, "All objects search themselves for $-commands.");
	else
	    notify(player, "Objects other than players search themselves for $-commands.");
    } else 
	notify(player, "Objects do not search themselves for $-commands.");

#ifdef NO_LAG_CHECK
    notify(player, "CPU usage warnings are disabled.");
#else
    notify(player, "CPU usage warnings are enabled.");
#endif /* NO_LAG_CHECK */

#ifdef FLOATING_POINTS
    notify(player, "MUSH arithmetic operations use floating-point numbers.");
#else
    notify(player, "MUSH arithmetic operations use integers.");
#endif				/* FLOATING_POINTS */

    if (mudconf.trace_topdown) {
	notify(player, "Trace output is presented top-down (whole expression first, then sub-exprs).");
	notify(player, tprintf("Only %d lines of trace output are displayed.",
			       mudconf.trace_limit));
    } else {
	notify(player, "Trace output is presented bottom-up (subexpressions first).");
    }

#ifdef PUEBLO_SUPPORT
    notify(player, "The Pueblo client HTML extensions are supported.");
#endif				/* PUEBLO_SUPPORT */

    if (mudconf.fascist_tport)
	notify(player, "You may only @teleport out of locations that are JUMP_OK or that you control.");

    if (mudconf.parent_control)
	notify(player, "The 'ControlLock' lock may be inherited from parents.");

    /* -- Configuration Options related to Speaking -- */

    if (mudconf.pemit_players)
	notify(player, "The '@pemit' command may be used to emit to faraway players.");
    else
	notify(player, "The '@pemit' command cannot be used to emit to faraway players.");

    if (mudconf.pemit_any)
	notify(player, "The '@pemit' command may be used to emit to any remote object.");
    else
	notify(player, "The '@pemit' command may not be used to emit to any remote object.");

#ifdef PARANOID_EMIT
    notify(player, "The '@emit' commands use strict permissions checking.");
#endif /* PARANOID_EMIT */

    if (mudconf.player_listen)
	notify(player, "The @Listen/@Ahear attribute set works on player objects.");
    else
	notify(player, "The @Listen/@Ahear attribute set does not work on player objects.");

#ifdef COMMA_IN_SAY
    notify(player, "There is a comma in the output of the 'say' command.");
#endif /* COMMA_IN_SAY */

    if (!mudconf.quiet_whisper)
	notify(player, "The 'whisper' command lets others in the room with you know you whispered.");

    if (!mudconf.robot_speak)
	notify(player, "Robots are not allowed to speak in public areas.");

    /* -- Default Command Behaviors -- */

    notify(player,
	   tprintf("The default switch for the '@switch' command is %s.",
		   switchd[mudconf.switch_df_all]));
    notify(player,
	   tprintf("The default switch for the 'examine' command is %s.",
		   examd[mudconf.exam_public]));

    /* -- Configuration Options related to Looking and other Information */

    if (mudconf.ex_flags)
	notify(player, "The 'examine' command lists the flag names for the object's flags.");
    if (mudconf.pub_flags)
	notify(player, "The 'flags()' function will return the flags of any object.");
    if (mudconf.read_rem_desc)
	notify(player, "The 'get()' function will return the description of faraway objects,");
    if (mudconf.read_rem_name)
	notify(player, "The 'name()' function will return the name of faraway objects.");

    if (!mudconf.quiet_look)
	notify(player, "The 'look' command shows visible attributes in addition to the description.");
    if (mudconf.see_own_dark)
	notify(player, "The 'look' command lists DARK objects owned by you.");
    if (!mudconf.dark_sleepers)
	notify(player, "The 'look' command shows disconnected players.");

    if (mudconf.terse_look)
	notify(player, "The 'look' command obeys the TERSE flag.");
    if (!mudconf.terse_contents)
	notify(player, "The TERSE flag suppresses listing the contents of a location.");
    if (!mudconf.terse_exits)
	notify(player, "The TERSE flag suppresses listing obvious exits in a location.");
    if (!mudconf.terse_movemsg)
	notify(player, "The TERSE flag suppresses enter/leave/succ/drop messages generated by moving.");

    if (mudconf.sweep_dark)
	notify(player, "Players may @sweep dark locations.");

    /* -- End of Mortal-Visible Configuration Options -- */

    if (!Wizard(player))
	return;
    buff = alloc_mbuf("list_options");

    notify(player,
	   tprintf("%d commands are run from the queue when there is no net activity.",
		   mudconf.queue_chunk));
    notify(player,
    tprintf("%d commands are run from the queue when there is net activity.",
	    mudconf.active_q_chunk));

    if (mudconf.idle_wiz_dark)
	notify(player, "Wizards idle for longer than the default timeout are automatically set DARK.");

    if (mudconf.safe_unowned)
	notify(player, "Objects not owned by you are automatically considered SAFE.");

    if (mudconf.paranoid_alloc)
	notify(player, "The buffer pools are checked for consistency on each allocate or free.");

    notify(player,
	   tprintf("The %s cache is %d entries wide by %d entries deep.",
		   CACHING, mudconf.cache_width, mudconf.cache_depth));
    if (mudconf.cache_names)
	notify(player, "A separate object name cache is used.");
    if (mudconf.cache_steal_dirty)
	notify(player, "Old modified cache entries may be reused when allocating a new entry.");

    if (mudconf.fork_dump) {
	notify(player, "Database dumps are performed by a fork()ed process.");
	if (mudconf.fork_vfork)
	    notify(player, "The 'vfork()' call is used to perform the fork.");
    }

    if (mudconf.use_global_aconn)
	notify(player, "Global aconnects and disconnects are executed by Master Room objects.");
    if (mudconf.local_masters) {
	notify(player, "Objects set ZONE are treated as local master rooms.");
	if (mudconf.parent_zones)
	    notify(player, 
	      "Rooms can have a secondary parent for ZONE command checks.");
    }

    if (mudconf.max_players >= 0)
	notify(player,
	       tprintf("There may be at most %d players logged in at once.",
		       mudconf.max_players));

    notify(player,
	   tprintf("Players are given %d %s each day they connect.",
		   mudconf.paycheck, mudconf.many_coins));
    notify(player,
	   tprintf("Earning money is difficult if you have more than %d %s.",
		   mudconf.paylimit, mudconf.many_coins));
    if (mudconf.payfind > 0)
	notify(player,
	       tprintf("Players have a 1 in %d chance of finding a %s each time they move.",
		       mudconf.payfind, mudconf.one_coin));

    notify(player,
	   tprintf("The head of the object freelist is #%d.",
		   mudstate.freelist));

    switch (mudconf.sig_action) {
    case SA_DFLT:
	notify(player, "Fatal signals are not caught.");
	break;
    case SA_RESIG:
	notify(player, "Fatal signals cause a panic dump and are resignalled from the signal handler.");
	break;
    case SA_RETRY:
	notify(player, "Fatal signals cause a panic dump and control returns to the point of signal.");
	break;
    }

    sprintf(buff, "Intervals: Dump...%d  Clean...%d  Idlecheck...%d  Rwho...%d",
	    mudconf.dump_interval, mudconf.check_interval,
	    mudconf.idle_interval, mudconf.rwho_interval);
    notify(player, buff);

    sprintf(buff, "Timers: Dump...%d  Clean...%d  Idlecheck...%d  Rwho...%d",
	    (int)(mudstate.dump_counter - now),
	    (int)(mudstate.check_counter - now),
	    (int)(mudstate.idle_counter - now),
	    (int)(mudstate.rwho_counter - now));
    notify(player, buff);

    sprintf(buff, "Timeouts: Idle...%d  Connect...%d  Tries...%d  Lag...%d",
	    mudconf.idle_timeout, mudconf.conn_timeout,
	    mudconf.retry_limit, mudconf.max_cmdsecs);
    notify(player, buff);

    sprintf(buff, "Scheduling: Timeslice...%d  Max_Quota...%d  Increment...%d",
	    mudconf.timeslice, mudconf.cmd_quota_max,
	    mudconf.cmd_quota_incr);
    notify(player, buff);

    sprintf(buff, "Compression: Spaces...%s", ed[mudconf.space_compress]);
    notify(player, buff);

    sprintf(buff, "Guests:  %d Guests active: ", mudconf.num_guests);
    for (i = 0; i < mudconf.num_guests; i++)
	sprintf(buff + strlen(buff), " #%d", mudconf.guest_chars[i]);
    notify(player, buff);

    sprintf(buff, "MasterRoom...#%d  StartRoom...#%d  StartHome...#%d  DefaultHome...#%d",
	    mudconf.master_room, mudconf.start_room,
	    mudconf.start_home, mudconf.default_home);
    notify(player, buff);
  
    sprintf(buff, "New characters:  %s...%d  Quota...%d  Rooms...%d  Exits...%d  Things...%d  Players...%d", 
	    mudconf.many_coins, mudconf.paystart, mudconf.start_quota, 
	    mudconf.start_room_quota, mudconf.start_exit_quota, 
	    mudconf.start_thing_quota, mudconf.start_player_quota);
    notify(player, buff);
  
    sprintf(buff, "Limits:  Output...%d  Recursion...%d  Invocation...%d  Parents...%d",
	    mudconf.output_limit, mudconf.func_nest_lim, mudconf.func_invk_lim,
	    mudconf.parent_nest_lim);
    notify(player, buff);
  
    sprintf(buff, "Rwho: Host...%s  Port...%d  Connection...%s  Transmit...%s",
	    mudconf.rwho_host, mudconf.rwho_info_port,
	    ud[mudstate.rwho_on], ed[mudconf.rwho_transmit]);
    notify(player, buff);

    free_mbuf(buff);
}

/* ---------------------------------------------------------------------------
 * list_vattrs: List user-defined attributes
 */

static void
list_vattrs(player)
    dbref player;
{
    VATTR *va;
    int na;
    char *buff;

    buff = alloc_lbuf("list_vattrs");
    notify(player, "--- User-Defined Attributes ---");
    for (va = vattr_first(), na = 0; va; va = vattr_next(va), na++) {
	if (!(va->flags & AF_DELETED)) {
	    sprintf(buff, "%s(%d):", va->name, va->number);
	    listset_nametab(player, attraccess_nametab, va->flags,
			    buff, 1);
	}
    }

    notify(player, tprintf("%d attributes, next=%d",
			   na, mudstate.attr_next));
    free_lbuf(buff);
}

/* ---------------------------------------------------------------------------
 * list_hashstats: List information from hash tables
 */

static void
list_hashstat(player, tab_name, htab)
    dbref player;
    HASHTAB *htab;
    const char *tab_name;
{
    char *buff;

    buff = hashinfo(tab_name, htab);
    notify(player, buff);
    free_mbuf(buff);
}

static void
list_nhashstat(player, tab_name, htab)
    dbref player;
    NHSHTAB *htab;
    const char *tab_name;
{
    char *buff;

    buff = nhashinfo(tab_name, htab);
    notify(player, buff);
    free_mbuf(buff);
}

static void
list_hashstats(player)
    dbref player;
{
    notify(player, "Hash Stats      Size Entries Deleted   Empty Lookups    Hits  Checks Longest");
    list_hashstat(player, "Commands", &mudstate.command_htab);
    list_hashstat(player, "Logged-out Cmds", &mudstate.logout_cmd_htab);
    list_hashstat(player, "Functions", &mudstate.func_htab);
    list_hashstat(player, "Flags", &mudstate.flags_htab);
    list_hashstat(player, "Attr names", &mudstate.attr_name_htab);
    list_nhashstat(player, "Attr numbers", &mudstate.attr_num_htab);
    list_hashstat(player, "Player Names", &mudstate.player_htab);
    list_nhashstat(player, "Net Descriptors", &mudstate.desc_htab);
    list_nhashstat(player, "Forwardlists", &mudstate.fwdlist_htab);
    list_nhashstat(player, "Overlaid $-cmds", &mudstate.parent_htab);
    list_hashstat(player, "News topics", &mudstate.news_htab);
    list_hashstat(player, "Help topics", &mudstate.help_htab);
    list_hashstat(player, "Wizhelp topics", &mudstate.wizhelp_htab);
    list_vhashstats(player);
}

/* These are from 'udb_cache.c'. */
extern time_t cs_ltime;
extern int cs_writes;		/* total writes */
extern int cs_reads;		/* total reads */
extern int cs_dbreads;		/* total read-throughs */
extern int cs_dbwrites;		/* total write-throughs */
extern int cs_dels;		/* total deletes */
extern int cs_checks;		/* total checks */
extern int cs_rhits;		/* total reads filled from cache */
extern int cs_ahits;		/* total reads filled active cache */
extern int cs_whits;		/* total writes to dirty cache */
extern int cs_fails;		/* attempts to grab nonexistent */
extern int cs_resets;		/* total cache resets */
extern int cs_syncs;		/* total cache syncs */
extern int cs_objects;		/* total cache size */
#ifdef IN_MEM_COMPRESSION
extern int strings_compressed;	/* total number of strings compressed */
extern int strings_decompressed;/* total number of strings decompressed */
extern int chars_in;		/* Total number of characters compressed */
extern int symbols_out;		/* Total number of symbols emitted */
#endif
/* ---------------------------------------------------------------------------
 * list_db_stats: Get useful info from the DB layer about hash stats, etc.
 */

static void
list_db_stats(player)
    dbref player;
{
    notify(player,
	   tprintf("DB Cache Stats   Writes       Reads  (over %d seconds)",
		   time(0) - cs_ltime));
    notify(player, tprintf("Calls      %12d%12d", cs_writes, cs_reads));
    notify(player, tprintf("Cache Hits %12d%12d  (%d in active cache)",
			   cs_whits, cs_rhits, cs_ahits));
    notify(player, tprintf("I/O        %12d%12d",
			   cs_dbwrites, cs_dbreads));
    notify(player, tprintf("\nDeletes    %12d", cs_dels));
    notify(player, tprintf("Checks     %12d", cs_checks));
    notify(player, tprintf("Resets     %12d", cs_resets));
    notify(player, tprintf("Syncs      %12d", cs_syncs));
    notify(player, tprintf("Cache Size %12d", cs_objects));
#ifdef IN_MEM_COMPRESSION
    notify(player, "Compression Statistics:");
    notify(player, tprintf("strings compressed %d", strings_compressed));
    notify(player, tprintf("strings decompressed %d", strings_decompressed));
    notify(player, tprintf("compression ratio %d:%d", chars_in,
			   symbols_out + (symbols_out >> 1)));
#endif
}

/* ---------------------------------------------------------------------------
 * list_process: List local resource usage stats of the mush process.
 * Adapted from code by Claudius@PythonMUCK,
 *     posted to the net by Howard/Dark_Lord.
 */

static void
list_process(player)
    dbref player;
{
    int pid, psize;

#ifdef HAVE_GETRUSAGE
    struct rusage usage;
#else
#ifdef HAVE_GET_PROCESS_STATS
    timeval_t *tv;
    struct process_stats *pst, *cpst;
#endif
#endif

    pid = getpid();
#ifdef HAVE_GETPAGESIZE
    psize = getpagesize();
#else
    psize = sysconf(NM_BLOODY_PAGE_SYMBOL);
#endif

    /* Go display everything */

    notify(player,
	   tprintf("Process ID:  %10d        %10d bytes per page",
		   pid, psize));

#ifdef HAVE_GETRUSAGE

    getrusage(RUSAGE_SELF, &usage);

    notify(player,
	   tprintf("Time used:   %10d user   %10d sys",
		   usage.ru_utime.tv_sec, usage.ru_stime.tv_sec));
    notify(player,
	   tprintf("Integral mem:%10d shared %10d private%10d stack",
		   usage.ru_ixrss, usage.ru_idrss, usage.ru_isrss));
    notify(player,
	   tprintf("Max res mem: %10d pages  %10d bytes",
		   usage.ru_maxrss, (usage.ru_maxrss * psize)));
    notify(player,
	   tprintf("Page faults: %10d hard   %10d soft   %10d swapouts",
		   usage.ru_majflt, usage.ru_minflt, usage.ru_nswap));
    notify(player,
	   tprintf("Disk I/O:    %10d reads  %10d writes",
		   usage.ru_inblock, usage.ru_oublock));
    notify(player,
	   tprintf("Network I/O: %10d in     %10d out",
		   usage.ru_msgrcv, usage.ru_msgsnd));
    notify(player,
	   tprintf("Context swi: %10d vol    %10d forced %10d sigs",
		   usage.ru_nvcsw, usage.ru_nivcsw, usage.ru_nsignals));
#else
 
#ifdef HAVE_GET_PROCESS_STATS	/* Sequent: Dynix/PTX */
 
    pst = (struct process_stats *) XMALLOC(sizeof(struct process_stats),
 					   "list_process_pst");
    cpst = (struct process_stats *) XMALLOC(sizeof(struct process_stats),
 					    "list_process_cpst");
    tv = (struct timeval *) XMALLOC(sizeof(struct timeval), 
 				    "list_process_tv");
 
    if (get_process_stats(tv, PS_SELF, pst, cpst) < 0) {
	free(tv);
	free(pst);
 	free(cpst);
 	return;
    }
 
    free(tv);
    free(cpst);
 
    notify(player,
 	   tprintf("Time used:   %10d user   %10d sys",
 		   pst->ps_utime.tv_sec, pst->ps_stime.tv_sec));
    notify(player,
 	   tprintf("Max res mem: %10d pages  %10d bytes",
 		   pst->ps_maxrss, (pst->ps_maxrss * psize)));
    notify(player,
 	   tprintf("Page faults: %10d hard   %10d soft   %10d swapouts",
 		   pst->ps_pagein, pst->ps_reclaim, pst->ps_swap));
    notify(player,
 	   tprintf("Disk I/O:    %10d reads  %10d writes",
 		   pst->ps_bread, pst->ps_bwrite));
    notify(player,
	   tprintf("Context swi: %10d vol    %10d forced %10d sigs",
		   pst->ps_volcsw, pst->ps_involcsw, pst->ps_signal));
    free(pst);

#endif				/* HAVE_GET_PROCESS_STATS */

#endif				/* HAVE_GETRUSAGE */
}

/* ---------------------------------------------------------------------------
 * do_list: List information stored in internal structures.
 */

#define	LIST_ATTRIBUTES	1
#define	LIST_COMMANDS	2
#define	LIST_COSTS	3
#define	LIST_FLAGS	4
#define	LIST_FUNCTIONS	5
#define	LIST_GLOBALS	6
#define	LIST_ALLOCATOR	7
#define	LIST_LOGGING	8
#define	LIST_DF_FLAGS	9
#define	LIST_PERMS	10
#define	LIST_ATTRPERMS	11
#define	LIST_OPTIONS	12
#define	LIST_HASHSTATS	13
#define	LIST_BUFTRACE	14
#define	LIST_CONF_PERMS	15
#define	LIST_SITEINFO	16
#define	LIST_POWERS	17
#define	LIST_SWITCHES	18
#define	LIST_VATTRS	19
#define	LIST_DB_STATS	20	/* GAC 4/6/92 */
#define	LIST_PROCESS	21
#define	LIST_BADNAMES	22

NAMETAB list_names[] =
{
    {(char *) "allocations", 2, CA_WIZARD, LIST_ALLOCATOR},
    {(char *) "attr_permissions", 5, CA_WIZARD, LIST_ATTRPERMS},
    {(char *) "attributes", 2, CA_PUBLIC, LIST_ATTRIBUTES},
    {(char *) "bad_names", 2, CA_WIZARD, LIST_BADNAMES},
    {(char *) "buffers", 2, CA_WIZARD, LIST_BUFTRACE},
    {(char *) "commands", 3, CA_PUBLIC, LIST_COMMANDS},
    {(char *) "config_permissions", 3, CA_GOD, LIST_CONF_PERMS},
    {(char *) "costs", 3, CA_PUBLIC, LIST_COSTS},
    {(char *) "db_stats", 2, CA_WIZARD, LIST_DB_STATS},
    {(char *) "default_flags", 1, CA_PUBLIC, LIST_DF_FLAGS},
    {(char *) "flags", 2, CA_PUBLIC, LIST_FLAGS},
    {(char *) "functions", 2, CA_PUBLIC, LIST_FUNCTIONS},
    {(char *) "globals", 1, CA_WIZARD, LIST_GLOBALS},
    {(char *) "hashstats", 1, CA_WIZARD, LIST_HASHSTATS},
    {(char *) "logging", 1, CA_GOD, LIST_LOGGING},
    {(char *) "options", 1, CA_PUBLIC, LIST_OPTIONS},
    {(char *) "permissions", 2, CA_WIZARD, LIST_PERMS},
/* {(char *)"powers",		2,	CA_WIZARD,	LIST_POWERS}, */
    {(char *) "process", 2, CA_WIZARD, LIST_PROCESS},
    {(char *) "site_information", 2, CA_WIZARD, LIST_SITEINFO},
    {(char *) "switches", 2, CA_PUBLIC, LIST_SWITCHES},
    {(char *) "user_attributes", 1, CA_WIZARD, LIST_VATTRS},
    {NULL, 0, 0, 0}};

extern NAMETAB enable_names[];
extern NAMETAB logoptions_nametab[];
extern NAMETAB logdata_nametab[];

void
do_list(player, cause, extra, arg)
    dbref player, cause;
    int extra;
    char *arg;
{
    int flagvalue;

    flagvalue = search_nametab(player, list_names, arg);
    switch (flagvalue) {
    case LIST_ALLOCATOR:
	list_bufstats(player);
	break;
    case LIST_BUFTRACE:
	list_buftrace(player);
	break;
    case LIST_ATTRIBUTES:
	list_attrtable(player);
	break;
    case LIST_COMMANDS:
	list_cmdtable(player);
	break;
    case LIST_SWITCHES:
	list_cmdswitches(player);
	break;
    case LIST_COSTS:
	list_costs(player);
	break;
    case LIST_OPTIONS:
	list_options(player);
	break;
    case LIST_HASHSTATS:
	list_hashstats(player);
	break;
    case LIST_SITEINFO:
	list_siteinfo(player);
	break;
    case LIST_FLAGS:
	display_flagtab(player);
	break;
    case LIST_FUNCTIONS:
	list_functable(player);
	break;
    case LIST_GLOBALS:
	interp_nametab(player, enable_names, mudconf.control_flags,
		       (char *) "Global parameters:", (char *) "enabled",
		       (char *) "disabled");
	break;
    case LIST_DF_FLAGS:
	list_df_flags(player);
	break;
    case LIST_PERMS:
	list_cmdaccess(player);
	break;
    case LIST_CONF_PERMS:
	list_cf_access(player);
	break;
    case LIST_POWERS:
	list_ntab_flags(player, powers_nametab, access_nametab);
	break;
    case LIST_ATTRPERMS:
	list_attraccess(player);
	break;
    case LIST_VATTRS:
	list_vattrs(player);
	break;
    case LIST_LOGGING:
	interp_nametab(player, logoptions_nametab, mudconf.log_options,
		       (char *) "Events Logged:", (char *) "enabled",
		       (char *) "disabled");
	interp_nametab(player, logdata_nametab, mudconf.log_info,
		       (char *) "Information Logged:", (char *) "yes",
		       (char *) "no");
	break;
    case LIST_DB_STATS:
	list_db_stats(player);
	break;
    case LIST_PROCESS:
	list_process(player);
	break;
    case LIST_BADNAMES:
	badname_list(player, "Disallowed names:");
	break;
    default:
	display_nametab(player, list_names,
			(char *) "Unknown option.  Use one of:", 1);
    }
}
