pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file command.c
 *
 * \brief Parsing of commands.
 *
 * Sets up a hash table for the commands, and parses input for commands.
 * This implementation is almost totally by Thorvald Natvig, with
 * various mods by Javelin and others over time.
 *
 */

#include "copyrite.h"
#include "config.h"

#include <string.h>

#include "conf.h"
#include "externs.h"
#include "dbdefs.h"
#include "mushdb.h"
#include "game.h"
#include "match.h"
#include "attrib.h"
#include "extmail.h"
#include "getpgsiz.h"
#include "parse.h"
#include "access.h"
#include "version.h"
#include "ptab.h"
#include "htab.h"
#include "function.h"
#include "command.h"
#include "mymalloc.h"
#include "flags.h"
#include "log.h"
#include "cmds.h"
#include "confmagic.h"

PTAB ptab_command;	/**< Prefix table for command names. */
PTAB ptab_command_perms;	/**< Prefix table for command permissions */

HASHTAB htab_reserved_aliases;	/**< Hash table for reserved command aliases */

static const char *command_isattr(char *command);
static int command_check(dbref player, COMMAND_INFO *cmd);
static int switch_find(COMMAND_INFO *cmd, char *sw);
static void strccat(char *buff, char **bp, const char *from);
static int has_hook(struct hook_data *hook);
extern int global_fun_invocations;	/**< Counter for function invocations */
extern int global_fun_recursions;	/**< Counter for function recursion */

int run_hook(dbref player, dbref cause, struct hook_data *hook,
	     char *saveregs[], int save);

/** The list of standard commands. Additional commands can be added
 * at runtime with add_command().
 */
COMLIST commands[] = {

  {"@COMMAND",
   "ADD ALIAS DELETE EQSPLIT LSARGS RSARGS NOEVAL ON OFF QUIET ENABLE DISABLE RESTRICT",
   cmd_command,
   CMD_T_PLAYER | CMD_T_EQSPLIT, 0, 0},
  {"@@", NULL, cmd_null, CMD_T_ANY | CMD_T_NOPARSE, 0, 0},
  {"@ALLHALT", NULL, cmd_allhalt, CMD_T_ANY, "WIZARD", "HALT"},
  {"@ALLQUOTA", "QUIET", cmd_allquota, CMD_T_ANY, "WIZARD", "QUOTA"},
  {"@ASSERT", NULL, cmd_assert, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, 0,
   0},
  {"@ATRLOCK", NULL, cmd_atrlock, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},
  {"@ATRCHOWN", NULL, cmd_atrchown, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},

  {"@ATTRIBUTE", "ACCESS DELETE RENAME RETROACTIVE", cmd_attribute,
   CMD_T_ANY | CMD_T_EQSPLIT, "WIZARD", 0},
  {"@BOOT", "PORT ME SILENT", cmd_boot, CMD_T_ANY, 0, 0},
  {"@BREAK", NULL, cmd_break, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, 0,
   0},
  {"@CEMIT", "NOEVAL NOISY SPOOF", cmd_cemit,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@CHANNEL",
   "LIST ADD DELETE RENAME NAME PRIVS QUIET NOISY DECOMPILE DESCRIBE CHOWN WIPE MUTE UNMUTE GAG UNGAG HIDE UNHIDE WHAT TITLE BRIEF RECALL BUFFER SET",
   cmd_channel,
   CMD_T_ANY | CMD_T_SWITCHES | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@CHAT", NULL, cmd_chat, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@CHOWNALL", "PRESERVE", cmd_chownall, CMD_T_ANY | CMD_T_EQSPLIT, "WIZARD",
   0},

  {"@CHOWN", "PRESERVE", cmd_chown,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@CHZONEALL", NULL, cmd_chzoneall, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},

  {"@CHZONE", NULL, cmd_chzone,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@CONFIG",
   "SET LOWERCASE LIST GLOBALS DEFAULTS COSTS FLAGS FUNCTIONS COMMANDS ATTRIBS",
   cmd_config, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},
  {"@CPATTR", "CONVERT NOFLAGCOPY", cmd_cpattr,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS,
   0, 0},
  {"@CREATE", NULL, cmd_create, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
   0, 0},
  {"@CLONE", "PRESERVE", cmd_clone, CMD_T_ANY | CMD_T_NOGAGGED | CMD_T_EQSPLIT,
   0, 0},

  {"@CLOCK", "JOIN SPEAK MOD SEE HIDE", cmd_clock,
   CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},
  {"@DBCK", NULL, cmd_dbck, CMD_T_ANY, "WIZARD", 0},

  {"@DECOMPILE", "DB PREFIX TF FLAGS ATTRIBS SKIPDEFAULTS", cmd_decompile,
   CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},
  {"@DESTROY", "OVERRIDE", cmd_destroy, CMD_T_ANY, 0, 0},

  {"@DIG", "TELEPORT", cmd_dig,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},
  {"@DISABLE", NULL, cmd_disable, CMD_T_ANY, "WIZARD", 0},

  {"@DOING", "HEADER", cmd_doing,
   CMD_T_ANY | CMD_T_NOPARSE | CMD_T_NOGAGGED, 0, 0},
  {"@DOLIST", "NOTIFY DELIMIT", cmd_dolist,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, 0, 0},
  {"@DRAIN", "ALL ANY", cmd_notify_drain, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},
  {"@DUMP", "PARANOID DEBUG", cmd_dump, CMD_T_ANY, "WIZARD", 0},

  {"@EDIT", "FIRST", cmd_edit,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE |
   CMD_T_NOGAGGED, 0, 0},
  {"@ELOCK", NULL, cmd_elock, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
   0, 0},
  {"@EMIT", "ROOM NOEVAL SILENT SPOOF", cmd_emit, CMD_T_ANY | CMD_T_NOGAGGED, 0,
   0},
  {"@ENABLE", NULL, cmd_enable, CMD_T_ANY | CMD_T_NOGAGGED, "WIZARD", 0},

  {"@ENTRANCES", "EXITS THINGS PLAYERS ROOMS", cmd_entrances,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},
  {"@EUNLOCK", NULL, cmd_eunlock, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},

  {"@FIND", NULL, cmd_find,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},
  {"@FIRSTEXIT", NULL, cmd_firstexit, CMD_T_ANY, 0, 0},
  {"@FLAG", "ADD TYPE LETTER LIST RESTRICT DELETE ALIAS DISABLE ENABLE",
   cmd_flag,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},

  {"@FORCE", "NOEVAL", cmd_force, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
   0, 0},
  {"@FUNCTION", "DELETE ENABLE DISABLE RESTORE RESTRICT", cmd_function,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},
  {"@GREP", "LIST PRINT ILIST IPRINT", cmd_grep,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE | CMD_T_NOGAGGED, 0, 0},
  {"@HALT", "ALL", cmd_halt, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},
  {"@HIDE", "NO OFF YES ON", cmd_hide, CMD_T_ANY, 0, 0},
  {"@HOOK", "AFTER BEFORE IGNORE OVERRIDE", cmd_hook,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS,
   "WIZARD", 0},
  {"@KICK", NULL, cmd_kick, CMD_T_ANY, "WIZARD", 0},

  {"@LEMIT", "NOEVAL SILENT SPOOF", cmd_lemit,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@LINK", "PRESERVE", cmd_link, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0,
   0},
  {"@LISTMOTD", NULL, cmd_listmotd, CMD_T_ANY, 0, 0},

  {"@LIST", "LOWERCASE MOTD FLAGS FUNCTIONS POWERS COMMANDS ATTRIBS", cmd_list,
   CMD_T_ANY, 0, 0},
  {"@LOCK", NULL, cmd_lock,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_SWITCHES | CMD_T_NOGAGGED, 0, 0},
  {"@LOG", "CHECK CMD CONN ERR TRACE WIZ", cmd_log,
   CMD_T_ANY | CMD_T_NOGAGGED, "WIZARD", 0},
  {"@LOGWIPE", "CHECK CMD CONN ERR TRACE WIZ", cmd_logwipe,
   CMD_T_ANY | CMD_T_NOGAGGED | CMD_T_GOD, 0, 0},
  {"@LSET", NULL, cmd_lset,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@MAIL",
   "NOEVAL NOSIG STATS DSTATS FSTATS DEBUG NUKE FOLDERS UNFOLDER LIST READ CLEAR UNCLEAR PURGE FILE TAG UNTAG FWD FORWARD SEND SILENT URGENT",
   cmd_mail, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},

  {"@MALIAS",
   "SET CREATE DESTROY DESCRIBE RENAME STATS CHOWN NUKE ADD REMOVE LIST ALL WHO MEMBERS USEFLAG SEEFLAG",
   cmd_malias, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},

  {"@MAP", "DELIMIT", cmd_map, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE,
   0, 0},
  {"@MOTD", "CONNECT LIST WIZARD DOWN FULL", cmd_motd,
   CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"@MVATTR", "CONVERT NOFLAGCOPY", cmd_mvattr,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS,
   0, 0},
  {"@NAME", NULL, cmd_name, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED
   | CMD_T_NOGUEST, 0, 0},
  {"@NEWPASSWORD", NULL, cmd_newpassword, CMD_T_ANY | CMD_T_EQSPLIT
   | CMD_T_RS_NOPARSE, "WIZARD", 0},
  {"@NOTIFY", "ALL ANY", cmd_notify_drain, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},
  {"@NSCEMIT", "NOEVAL NOISY SPOOF", cmd_cemit,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "WIZARD", "CAN_NSPEMIT"},
  {"@NSEMIT", "ROOM NOEVAL SILENT SPOOF", cmd_emit, CMD_T_ANY | CMD_T_NOGAGGED,
   "WIZARD", "CAN_NSPEMIT"},
  {"@NSLEMIT", "NOEVAL SILENT SPOOF", cmd_lemit,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "WIZARD", "CAN_NSPEMIT"},
  {"@NSOEMIT", "NOEVAL SPOOF", cmd_oemit,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "WIZARD", "CAN_NSPEMIT"},
  {"@NSPEMIT", "LIST SILENT NOISY NOEVAL", cmd_pemit,
   CMD_T_ANY | CMD_T_EQSPLIT, "WIZARD", "CAN_NSPEMIT"},
  {"@NSREMIT", "LIST NOEVAL NOISY SILENT SPOOF", cmd_remit,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "WIZARD", "CAN_NSPEMIT"},
  {"@NSZEMIT", NULL, cmd_zemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
   "WIZARD", "CAN_NSPEMIT"},
  {"@NUKE", NULL, cmd_nuke, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},

  {"@OEMIT", "NOEVAL SPOOF", cmd_oemit,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@OPEN", NULL, cmd_open,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},
  {"@PARENT", NULL, cmd_parent, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},
  {"@PASSWORD", NULL, cmd_password, CMD_T_PLAYER | CMD_T_EQSPLIT
   | CMD_T_NOPARSE | CMD_T_RS_NOPARSE | CMD_T_NOGUEST, 0, 0},
  {"@PCREATE", NULL, cmd_pcreate, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},

  {"@PEMIT", "LIST CONTENTS SILENT NOISY NOEVAL SPOOF", cmd_pemit,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@POLL", NULL, cmd_poll, CMD_T_ANY, 0, 0},
  {"@POOR", NULL, cmd_poor, CMD_T_ANY, 0, 0},
  {"@POWER", "ADD TYPE LETTER LIST RESTRICT DELETE ALIAS DISABLE ENABLE",
   cmd_power, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, "WIZARD", 0},
  {"@PS", "ALL SUMMARY COUNT QUICK", cmd_ps, CMD_T_ANY, 0, 0},
  {"@PURGE", NULL, cmd_purge, CMD_T_ANY, 0, 0},
  {"@QUOTA", "ALL SET", cmd_quota, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},
  {"@READCACHE", NULL, cmd_readcache, CMD_T_ANY, "WIZARD", 0},
  {"@RECYCLE", "OVERRIDE", cmd_destroy, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},

  {"@REMIT", "LIST NOEVAL NOISY SILENT SPOOF", cmd_remit,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@REJECTMOTD", NULL, cmd_rejectmotd, CMD_T_ANY, "WIZARD", 0},
  {"@RESTART", "ALL", cmd_restart, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"@RWALL", "NOEVAL EMIT", cmd_rwall, CMD_T_ANY, "WIZARD ROYALTY", 0},
  {"@SCAN", "ROOM SELF ZONE GLOBALS", cmd_scan,
   CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"@SEARCH", NULL, cmd_search,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE, 0, 0},
  {"@SELECT", "NOTIFY REGEXP", cmd_select,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE, 0, 0},
  {"@SET", NULL, cmd_set, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@SHUTDOWN", "PANIC REBOOT PARANOID", cmd_shutdown, CMD_T_ANY, "WIZARD", 0},
#ifdef HAS_MYSQL
  {"@SQL", NULL, cmd_sql, CMD_T_ANY, "WIZARD", "SQL_OK"},
#else
  {"@SQL", NULL, cmd_unimplemented, CMD_T_ANY, "WIZARD", "SQL_OK"},
#endif
  {"@SITELOCK", "BAN CHECK REGISTER REMOVE NAME", cmd_sitelock,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, "WIZARD", 0},
  {"@STATS", "CHUNKS FREESPACE PAGING REGIONS TABLES", cmd_stats,
   CMD_T_ANY, 0, 0},

  {"@SWEEP", "CONNECTED HERE INVENTORY EXITS", cmd_sweep, CMD_T_ANY, 0, 0},
  {"@SWITCH", "NOTIFY FIRST ALL REGEXP", cmd_switch,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE |
   CMD_T_NOGAGGED, 0, 0},
  {"@SQUOTA", NULL, cmd_squota, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},

  {"@TELEPORT", "SILENT INSIDE", cmd_teleport,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"@TRIGGER", NULL, cmd_trigger,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},
  {"@ULOCK", NULL, cmd_ulock, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
   0, 0},
  {"@UNDESTROY", NULL, cmd_undestroy, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"@UNLINK", NULL, cmd_unlink, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},

  {"@UNLOCK", NULL, cmd_unlock,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_SWITCHES | CMD_T_NOGAGGED, 0, 0},
  {"@UNRECYCLE", NULL, cmd_undestroy, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"@UPTIME", "MORTAL", cmd_uptime, CMD_T_ANY, 0, 0},
  {"@UUNLOCK", NULL, cmd_uunlock, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"@VERB", NULL, cmd_verb, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, 0, 0},
  {"@VERSION", NULL, cmd_version, CMD_T_ANY, 0, 0},
  {"@WAIT", "UNTIL", cmd_wait, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE,
   0, 0},
  {"@WALL", "NOEVAL EMIT", cmd_wall, CMD_T_ANY, "WIZARD ROYALTY", "ANNOUNCE"},

  {"@WARNINGS", NULL, cmd_warnings, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0},
  {"@WCHECK", "ALL ME", cmd_wcheck, CMD_T_ANY, 0, 0},
  {"@WHEREIS", NULL, cmd_whereis, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"@WIPE", NULL, cmd_wipe, CMD_T_ANY, 0, 0},
  {"@WIZWALL", "NOEVAL EMIT", cmd_wizwall, CMD_T_ANY, "WIZARD", 0},
  {"@WIZMOTD", NULL, cmd_wizmotd, CMD_T_ANY, "WIZARD", 0},
  {"@ZEMIT", NULL, cmd_zemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
   0, 0},

  {"BUY", NULL, cmd_buy, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"BRIEF", NULL, cmd_brief, CMD_T_ANY, 0, 0},
  {"DESERT", NULL, cmd_desert, CMD_T_PLAYER | CMD_T_THING, 0, 0},
  {"DISMISS", NULL, cmd_dismiss, CMD_T_PLAYER | CMD_T_THING, 0, 0},
  {"DROP", NULL, cmd_drop, CMD_T_PLAYER | CMD_T_THING, 0, 0},
  {"EXAMINE", "ALL BRIEF DEBUG MORTAL PARENT", cmd_examine, CMD_T_ANY, 0, 0},
  {"EMPTY", NULL, cmd_empty, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, 0, 0},
  {"ENTER", NULL, cmd_enter, CMD_T_ANY, 0, 0},

  {"FOLLOW", NULL, cmd_follow,
   CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, 0, 0},

  {"GET", NULL, cmd_get, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, 0, 0},
  {"GIVE", "SILENT", cmd_give, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0,
   0},
  {"GOTO", NULL, cmd_goto, CMD_T_PLAYER | CMD_T_THING, 0, 0},
  {"INVENTORY", NULL, cmd_inventory, CMD_T_ANY, 0, 0},

  {"KILL", NULL, cmd_kill, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"LOOK", "OUTSIDE", cmd_look, CMD_T_ANY, 0, 0},
  {"LEAVE", NULL, cmd_leave, CMD_T_PLAYER | CMD_T_THING, 0, 0},

  {"PAGE", "BLIND NOEVAL LIST PORT OVERRIDE", cmd_page,
   CMD_T_ANY | CMD_T_RS_NOPARSE | CMD_T_NOPARSE | CMD_T_EQSPLIT |
   CMD_T_NOGAGGED, 0, 0},
  {"POSE", "NOEVAL NOSPACE", cmd_pose, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"SCORE", NULL, cmd_score, CMD_T_ANY, 0, 0},
  {"SAY", "NOEVAL", cmd_say, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"SEMIPOSE", "NOEVAL", cmd_semipose, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},
  {"SLAY", NULL, cmd_slay, CMD_T_ANY, 0, 0},

  {"TAKE", NULL, cmd_take, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED,
   0, 0},
  {"TEACH", NULL, cmd_teach, CMD_T_ANY | CMD_T_NOPARSE, 0, 0},
  {"THINK", "NOEVAL", cmd_think, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},

  {"UNFOLLOW", NULL, cmd_unfollow,
   CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, 0, 0},
  {"USE", NULL, cmd_use, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0},

  {"WHISPER", "LIST NOISY SILENT NOEVAL", cmd_whisper,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
  {"WITH", "NOEVAL ROOM", cmd_with, CMD_T_PLAYER | CMD_T_THING | CMD_T_EQSPLIT,
   0, 0},

/* ATTRIB_SET is an undocumented command - it's sugar to make it possible
 * enable/disable attribute setting with &XX or @XX
 */
  {"ATTRIB_SET", NULL, command_atrset,
   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED | CMD_T_INTERNAL, 0, 0},

/* A way to stop people starting commands with functions */
  {"WARN_ON_MISSING", NULL, cmd_warn_on_missing,
   CMD_T_ANY | CMD_T_NOPARSE | CMD_T_INTERNAL, 0, 0},

/* A way to let people override the Huh? message */
  {"HUH_COMMAND", NULL, cmd_huh_command,
   CMD_T_ANY | CMD_T_NOPARSE | CMD_T_INTERNAL, 0, 0},

  {NULL, NULL, NULL, 0, 0, 0}
};


/* switch_list is defined in switchinc.c */
#include "switchinc.c"

/** Table of command permissions/restrictions. */
struct command_perms_t command_perms[] = {
  {"player", CMD_T_PLAYER},
  {"thing", CMD_T_THING},
  {"exit", CMD_T_EXIT},
  {"room", CMD_T_ROOM},
  {"any", CMD_T_ANY},
  {"god", CMD_T_GOD},
  {"nobody", CMD_T_DISABLED},
  {"nogagged", CMD_T_NOGAGGED},
  {"noguest", CMD_T_NOGUEST},
  {"nofixed", CMD_T_NOFIXED},
  {"logargs", CMD_T_LOGARGS},
  {"logname", CMD_T_LOGNAME},
#ifdef DANGEROUS
  {"listed", CMD_T_LISTED},
  {"switches", CMD_T_SWITCHES},
  {"internal", CMD_T_INTERNAL},
  {"ls_space", CMD_T_LS_SPACE},
  {"ls_noparse", CMD_T_LS_NOPARSE},
  {"rs_space", CMD_T_RS_SPACE},
  {"rs_noparse", CMD_T_RS_NOPARSE},
  {"eqsplit", CMD_T_EQSPLIT},
  {"ls_args", CMD_T_LS_ARGS},
  {"rs_args", CMD_T_RS_ARGS},
#endif
  {NULL, 0}
};


static void
strccat(char *buff, char **bp, const char *from)
{
  if (*buff)
    safe_str(", ", buff, bp);
  safe_str(from, buff, bp);
}
static int
switch_find(COMMAND_INFO *cmd, char *sw)
{
  SWITCH_VALUE *sw_val;
  int i = 0;
  int len;
  if (!sw || !*sw)
    return 0;
  len = strlen(sw);
  /* Special case, for init */
  sw_val = switch_list;
  if (!cmd) {
    while (sw_val->name) {
      if (strcmp(sw_val->name, sw) == 0)
	return sw_val->value;
      sw_val++;
    }
    return 0;
  } else {
    while (sw_val->name) {
      i++;
      if (SW_ISSET(cmd->sw, i) && (strncmp(sw_val->name, sw, len) == 0))
	return i;
      sw_val++;
    }
  }
  return 0;
}

/** Allocate and populate a COMMAND_INFO structure.
 * This function generates a new COMMAND_INFO structure, populates it
 * with given values, and returns a pointer. It should not be used
 * for local hacks - use command_add() instead.
 * \param name command name.
 * \param type types of objects that can use the command.
 * \param flagmask mask of flags (one is sufficient to use the command).
 * \param powers mask of powers (one is sufficient to use the command).
 * \param sw mask of switches the command accepts.
 * \param func function to call when the command is executed.
 * \return pointer to a newly allocated COMMAND_INFO structure.
 */
COMMAND_INFO *
make_command(const char *name, int type,
	     object_flag_type flagmask, object_flag_type powers,
	     switch_mask *sw, command_func func)
{
  COMMAND_INFO *cmd;
  cmd = (COMMAND_INFO *) mush_malloc(sizeof(COMMAND_INFO), "command");
  memset(cmd, 0, sizeof(COMMAND_INFO));
  cmd->name = name;
  cmd->restrict_message = NULL;
  cmd->func = func;
  cmd->type = type;
  cmd->flagmask = flagmask;
  cmd->powers = powers;
  if (sw)
    memcpy(cmd->sw, sw, sizeof(switch_mask));
  else
    SW_ZERO(cmd->sw);
  cmd->hooks.before.obj = NOTHING;
  cmd->hooks.before.attrname = NULL;
  cmd->hooks.after.obj = NOTHING;
  cmd->hooks.after.attrname = NULL;
  cmd->hooks.ignore.obj = NOTHING;
  cmd->hooks.ignore.attrname = NULL;
  cmd->hooks.override.obj = NOTHING;
  cmd->hooks.override.attrname = NULL;
  return cmd;
}

/** Add a new command to the command table.
 * This function is the top-level function for adding a new command.
 * \param name name of the command.
 * \param type types of objects that can use the command.
 * \param flagstr space-separated list of flags sufficient to use command.
 * \param powerstr space-separated list of powers sufficient to use command.
 * \param switchstr space-separated list of switches for the command.
 * \param func function to call when command is executed.
 * \return pointer to a newly allocated COMMAND_INFO entry or NULL.
 */
COMMAND_INFO *
command_add(const char *name, int type, const char *flagstr,
	    const char *powerstr, const char *switchstr, command_func func)
{
  object_flag_type flagmask = NULL, powers = NULL;
  switch_mask *sw = switchmask(switchstr);

  if (flagstr)
    flagmask = string_to_bits("FLAG", flagstr);
  if (powerstr)
    powers = string_to_bits("POWER", powerstr);
  ptab_start_inserts(&ptab_command);
  ptab_insert(&ptab_command, name,
	      make_command(name, type, flagmask, powers, sw, func));
  ptab_end_inserts(&ptab_command);
  return command_find(name);
}


/** Search for a command by (partial) name.
 * This function searches the command table for a (partial) name match
 * and returns a pointer to the COMMAND_INFO entry. It returns NULL
 * if the name given is a reserved alias or if no command table entry
 * matches.
 * \param name name of command to match.
 * \return pointer to a COMMAND_INFO entry, or NULL.
 */
COMMAND_INFO *
command_find(const char *name)
{

  char cmdname[BUFFER_LEN];
  strcpy(cmdname, name);
  upcasestr(cmdname);
  if (hash_find(&htab_reserved_aliases, cmdname))
    return NULL;
  return (COMMAND_INFO *) ptab_find(&ptab_command, cmdname);
}

/** Search for a command by exact name.
 * This function searches the command table for an exact name match
 * and returns a pointer to the COMMAND_INFO entry. It returns NULL
 * if the name given is a reserved alias or if no command table entry
 * matches.
 * \param name name of command to match.
 * \return pointer to a COMMAND_INFO entry, or NULL.
 */
COMMAND_INFO *
command_find_exact(const char *name)
{

  char cmdname[BUFFER_LEN];
  strcpy(cmdname, name);
  upcasestr(cmdname);
  if (hash_find(&htab_reserved_aliases, cmdname))
    return NULL;
  return (COMMAND_INFO *) ptab_find_exact(&ptab_command, cmdname);
}


/** Modify a command's entry in the table.
 * Given a command name and other parameters, look up the command
 * in the table, and if it's there, modify the parameters.
 * \param name name of command to modify.
 * \param type new types for command, or -1 to leave unchanged.
 * \param flagmask new mask of flags for command, or NULL to leave unchanged.
 * \param powers new mask of powers for command, or NULL to leave unchanged.
 * \param sw new mask of switches for command, or NULL to leave unchanged.
 * \param func new function to call, or NULL to leave unchanged.
 * \return pointer to modified command entry, or NULL.
 */
COMMAND_INFO *
command_modify(const char *name, int type,
	       object_flag_type flagmask, object_flag_type powers,
	       switch_mask *sw, command_func func)
{
  COMMAND_INFO *cmd;
  cmd = command_find(name);
  if (!cmd)
    return NULL;
  if (type != -1)
    cmd->type = type;
  if (flagmask)
    cmd->flagmask = flagmask;
  if (powers)
    cmd->powers = powers;
  if (sw)
    memcpy(cmd->sw, sw, sizeof(switch_mask));
  if (func)
    cmd->func = func;
  return cmd;
}

/** Convert a switch string to a switch mask.
 * Given a space-separated list of switches in string form, return
 * a pointer to a static switch mask.
 * \param switches list of switches as a string.
 * \return pointer to a static switch mask.
 */
switch_mask *
switchmask(const char *switches)
{
  static switch_mask sw;
  char buff[BUFFER_LEN];
  char *p, *s;
  int switchnum;
  SW_ZERO(sw);
  if (!switches || !switches[0])
    return NULL;
  strcpy(buff, switches);
  p = buff;
  while (p) {
    s = split_token(&p, ' ');
    switchnum = switch_find(NULL, s);
    if (!switchnum)
      return NULL;
    else
      SW_SET(sw, switchnum);
  }
  return &sw;
}

/** Add an alias to the table of reserved aliases.
 * This function adds an alias to the table of reserved aliases, preventing
 * it from being matched for standard commands. It's typically used to
 * insure that 'e' will match a global 'east;e' exit rather than the
 * 'examine' command.
 * \param a alias to reserve.
 */
void
reserve_alias(const char *a)
{
  static char placeholder[2] = "x";
  hashadd(strupper(a), (void *) placeholder, &htab_reserved_aliases);
}

/** Initialize command tables (before reading config file).
 * This function performs command table initialization that should take place
 * before the configuration file has been read. It initializes the
 * command prefix table and the reserved alias table, inserts all of the
 * commands from the commands array into the prefix table, initializes
 * the command permission prefix table, and inserts all the permissions
 * from the command_perms array into the prefix table. Finally, it
 * calls local_commands() to do any cmdlocal.c work.
 */
void
command_init_preconfig(void)
{
  struct command_perms_t *c;
  COMLIST *cmd;
  static int done = 0;
  if (done == 1)
    return;
  done = 1;

  ptab_init(&ptab_command);
  hashinit(&htab_reserved_aliases, 16, sizeof(COMMAND_INFO));
  reserve_aliases();
  ptab_start_inserts(&ptab_command);
  for (cmd = commands; cmd->name; cmd++) {
    ptab_insert(&ptab_command, cmd->name,
		make_command(cmd->name, cmd->type,
			     string_to_bits("FLAG", cmd->flagstr),
			     string_to_bits("POWER", cmd->powers),
			     switchmask(cmd->switches), cmd->func));
  }
  ptab_end_inserts(&ptab_command);

  ptab_init(&ptab_command_perms);
  ptab_start_inserts(&ptab_command_perms);
  for (c = command_perms; c->name; c++)
    ptab_insert(&ptab_command_perms, c->name, c);
  ptab_end_inserts(&ptab_command_perms);

  local_commands();
}

/** Initialize commands (after reading config file).
 * This function performs command initialization that should take place
 * after the configuration file has been read.
 * Currently, there isn't any.
 */
void
command_init_postconfig(void)
{
  return;
}


/** Alias a command.
 * Given a command name and an alias for it, install the alias.
 * \param command canonical command name.
 * \param alias alias to associate with command.
 * \retval 0 failure (couldn't locate command).
 * \retval 1 success.
 */
int
alias_command(const char *command, const char *alias)
{
  COMMAND_INFO *cmd;

  /* Make sure the alias doesn't exit already */
  if (command_find_exact(alias))
    return 0;

  /* Look up the original */
  cmd = command_find_exact(command);
  if (!cmd)
    return 0;

  ptab_start_inserts(&ptab_command);
  ptab_insert(&ptab_command, strupper(alias), cmd);
  ptab_end_inserts(&ptab_command);
  return 1;
}

/* This is set to true for EQ_SPLIT commands that actually have a rhs.
 * Used in @teleport, ATTRSET and possibly other checks. It's ugly.
 * Blame Talek for it. ;)
 */
int rhs_present;

/** Parse the command arguments into arrays.
 * This function does the real work of parsing command arguments into
 * argument arrays. It is called separately to parse the left and
 * right sides of commands that are split at equal signs.
 * \param player the enactor.
 * \param cause the dbref causing the execution.
 * \param from pointer to address of where to parse arguments from.
 * \param to string to store parsed arguments into.
 * \param argv array of parsed arguments.
 * \param cmd pointer to command data.
 * \param right_side if true, parse on the right of the =. Otherwise, left.
 * \param forcenoparse if true, do no evaluation during parsing.
 */
void
command_argparse(dbref player, dbref cause, char **from, char *to,
		 char *argv[], COMMAND_INFO *cmd, int right_side,
		 int forcenoparse)
{
  int parse, split, args, i, done;
  char *t, *f;
  char *aold;

  f = *from;

  parse =
    (right_side) ? (cmd->type & CMD_T_RS_NOPARSE) : (cmd->type & CMD_T_NOPARSE);
  if (parse || forcenoparse)
    parse = PE_NOTHING;
  else
    parse = PE_DEFAULT | PE_COMMAND_BRACES;

  if (right_side)
    split = PT_NOTHING;
  else
    split = (cmd->type & CMD_T_EQSPLIT) ? PT_EQUALS : PT_NOTHING;

  if (right_side) {
    if (cmd->type & CMD_T_RS_ARGS)
      args = (cmd->type & CMD_T_RS_SPACE) ? PT_SPACE : PT_COMMA;
    else
      args = 0;
  } else {
    if (cmd->type & CMD_T_LS_ARGS)
      args = (cmd->type & CMD_T_LS_SPACE) ? PT_SPACE : PT_COMMA;
    else
      args = 0;
  }

  if ((parse == PE_NOTHING) && args)
    parse = PE_COMMAND_BRACES;

  i = 1;
  done = 0;
  *to = '\0';

  if (args) {
    t = to + 1;
  } else {
    t = to;
  }

  while (*f && !done) {
    aold = t;
    while (*f == ' ')
      f++;
    process_expression(to, &t, (const char **) &f, player, cause, cause,
		       parse, (split | args), NULL);
    *t = '\0';
    if (args) {
      argv[i] = aold;
      if (*f)
	f++;
      i++;
      t++;
      if (i == MAX_ARG)
	done = 1;
    }
    if (split && (*f == '=')) {
      rhs_present = 1;
      f++;
      *from = f;
      done = 1;
    }
  }

  *from = f;

  if (args)
    while (i < MAX_ARG)
      argv[i++] = NULL;
}

/** Determine whether a command is an attribute to set an attribute.
 * Is this command an attempt to set an attribute like @VA or &NUM?
 * If so, return the attrib's name. Otherwise, return NULL
 * \param command command string (first word of input).
 * \return name of the attribute to be set, or NULL.
 */
static const char *
command_isattr(char *command)
{
  ATTR *a;
  char buff[BUFFER_LEN];
  char *f, *t;

  if (((command[0] == '&') && (command[1])) ||
      ((command[0] == '@') && (command[1] == '_') && (command[2]))) {
    /* User-defined attributes: @_NUM or &NUM */
    if (command[0] == '@')
      return command + 2;
    else
      return command + 1;
  } else if (command[0] == '@') {
    f = command + 1;
    buff[0] = '@';
    t = buff + 1;
    while ((*f) && (*f != '/'))
      *t++ = *f++;
    *t = '\0';
    /* @-commands have priority over @-attributes with the same name */
    if (command_find(buff))
      return NULL;
    a = atr_match(buff + 1);
    if (a)
      return AL_NAME(a);
  }
  return NULL;
}

/** A handy macro to free up the command_parse-allocated variables */
#define command_parse_free_args \
    mush_free((Malloc_t) command, "string"); \
    mush_free((Malloc_t) swtch, "string"); \
    mush_free((Malloc_t) ls, "string"); \
    mush_free((Malloc_t) rs, "string"); \
    mush_free((Malloc_t) switches, "string")

/** Parse commands.
 * Parse the commands. This is the big one!
 * We distinguish parsing of input sent from a player at a socket
 * (in which case attribute values to set are not evaluated) and
 * input sent in any other way (in which case attribute values to set
 * are evaluated, and attributes are set NO_COMMAND).
 * Return NULL if the command was recognized and handled, the evaluated
 * text to match against $-commands otherwise.
 * \param player the enactor.
 * \param cause dbref that caused the command to be executed.
 * \param string the input to be parsed.
 * \param fromport if true, command was typed by a player at a socket.
 * \return NULL if a command was handled, otherwise the evaluated input.
 */
char *
command_parse(dbref player, dbref cause, char *string, int fromport)
{
  char *command, *swtch, *ls, *rs, *switches;
  static char commandraw[BUFFER_LEN];
  static char exit_command[BUFFER_LEN], *ec;
  char *lsa[MAX_ARG];
  char *rsa[MAX_ARG];
  char *ap, *swp;
  const char *attrib, *replacer;
  COMMAND_INFO *cmd;
  char *p, *t, *c, *c2;
  char command2[BUFFER_LEN];
  char b;
  int switchnum;
  switch_mask sw;
  int noeval;
  int noevtoken = 0;
  char *retval;

  rhs_present = 0;

  command = (char *) mush_malloc(BUFFER_LEN, "string");
  swtch = (char *) mush_malloc(BUFFER_LEN, "string");
  ls = (char *) mush_malloc(BUFFER_LEN, "string");
  rs = (char *) mush_malloc(BUFFER_LEN, "string");
  switches = (char *) mush_malloc(BUFFER_LEN, "string");
  if (!command || !swtch || !ls || !rs || !switches)
    mush_panic("Couldn't allocate memory in command_parse");
  p = string;
  replacer = NULL;
  attrib = NULL;
  cmd = NULL;
  c = command;
  /* All those one character commands.. Sigh */

  global_fun_invocations = global_fun_recursions = 0;
  if (*p == NOEVAL_TOKEN) {
    noevtoken = 1;
    p = string + 1;
    string = p;
    memmove(global_eval_context.ccom, (char *) global_eval_context.ccom + 1,
	    BUFFER_LEN - 1);
  }
  if (*p == '[') {
    if ((cmd = command_find("WARN_ON_MISSING"))) {
      if (!(cmd->type & CMD_T_DISABLED)) {
	cmd->func(cmd, player, cause, sw, string, NULL, NULL, ls, lsa, rs, rsa);
	command_parse_free_args;
	return NULL;
      }
    }
  }
  switch (*p) {
  case '\0':
    /* Just in case. You never know */
    command_parse_free_args;
    return NULL;
  case SAY_TOKEN:
    replacer = "SAY";
    if (CHAT_STRIP_QUOTE)
      p--;			/* Since 'say' strips out the '"' */
    break;
  case POSE_TOKEN:
    replacer = "POSE";
    break;
  case SEMI_POSE_TOKEN:
    if (*(p + 1) && *(p + 1) == ' ')
      replacer = "POSE";
    else
      replacer = "SEMIPOSE";
    break;
  case EMIT_TOKEN:
    replacer = "@EMIT";
    break;
  case CHAT_TOKEN:
#ifdef CHAT_TOKEN_ALIAS
  case CHAT_TOKEN_ALIAS:
#endif
    /* parse_chat() destructively modifies the command to replace
     * the first space with a '=' if the command is an actual
     * chat command */
    if (parse_chat(player, p + 1) && command_check_byname(player, "@CHAT")) {
      /* This is a "+chan foo" chat style
       * We set noevtoken to keep its noeval way, and
       * set the cmd to allow @hook. */
      replacer = "@CHAT";
      noevtoken = 1;
    }
  case NUMBER_TOKEN:
    /* parse_force() destructively modifies the command to replace
     * the first space with a '=' if the command is an actual
     * chat command */
    if (Mobile(player) && parse_force(p)) {
      /* This is a "#obj foo" force style
       * We set noevtoken to keep its noeval way, and
       * set the cmd to allow @hook. */
      replacer = "@FORCE";
      noevtoken = 1;
    }
  }

  if (replacer) {
    cmd = command_find(replacer);
    if (*p != NUMBER_TOKEN)
      p++;
  } else {
    /* At this point, we have not done a replacer, so we continue with the 
     * usual processing. Exits have next priority.  We still pass them 
     * through the parser so @hook on GOTO can work on them.
     */
    if (can_move(player, p)) {
      ec = exit_command;
      safe_str("GOTO ", exit_command, &ec);
      safe_str(p, exit_command, &ec);
      *ec = '\0';
      p = string = exit_command;
      noevtoken = 1;		/* But don't parse the exit name! */
    }
    c = command;
    while (*p == ' ')
      p++;
    process_expression(command, &c, (const char **) &p, player, cause, cause,
		       noevtoken ? PE_NOTHING :
		       ((PE_DEFAULT & ~PE_FUNCTION_CHECK) |
			PE_COMMAND_BRACES), PT_SPACE, NULL);
    *c = '\0';
    strcpy(commandraw, command);
    upcasestr(command);

    /* Catch &XX and @XX attribute pairs. If that's what we've got,
     * use the magical ATTRIB_SET command
     */
    attrib = command_isattr(command);
    if (attrib) {
      cmd = command_find("ATTRIB_SET");
    } else {
      c = command;
      while ((*c) && (*c != '/') && (*c != ' '))
	c++;
      b = *c;
      *c = '\0';
      cmd = command_find(command);
      *c = b;
      /* Is this for internal use? If so, players can't use it! */
      if (cmd && (cmd->type & CMD_T_INTERNAL))
	cmd = NULL;
    }
  }

  /* Set up commandraw for future use. This will contain the canonicalization
   * of the command name and may later have the parsed rest of the input
   * appended at the position pointed to by c2.
   */
  c2 = c;
  if (!cmd) {
    c2 = commandraw + strlen(commandraw);
  } else {
    if (replacer) {
      /* These commands don't allow switches, and need a space
       * added after their canonical name
       */
      c2 = commandraw;
      safe_str(cmd->name, commandraw, &c2);
      safe_chr(' ', commandraw, &c2);
    } else if (*c2 == '/') {
      /* Oh... DAMN */
      c2 = commandraw;
      strcpy(switches, commandraw);
      safe_str(cmd->name, commandraw, &c2);
      t = strchr(switches, '/');
      safe_str(t, commandraw, &c2);
    } else {
      c2 = commandraw;
      safe_str(cmd->name, commandraw, &c2);
    }
  }

  /* Test if this either isn't a command, or is a disabled one
   * If so, return Fully Parsed string for further processing.
   */

  if (!cmd || (cmd->type & CMD_T_DISABLED)) {
    if (*p) {
      if (*p == ' ') {
	safe_chr(' ', commandraw, &c2);
	p++;
      }
      process_expression(commandraw, &c2, (const char **) &p, player, cause,
			 cause, noevtoken ? PE_NOTHING :
			 ((PE_DEFAULT & ~PE_FUNCTION_CHECK) |
			  PE_COMMAND_BRACES), PT_DEFAULT, NULL);
    }
    *c2 = '\0';
    command_parse_free_args;
    return commandraw;
  }
  /* Check the permissions */
  if (!command_check(player, cmd)) {
    command_parse_free_args;
    return NULL;
  }
  /* Parse out any switches */
  SW_ZERO(sw);
  swp = switches;
  *swp = '\0';

  t = NULL;

  /* Don't parse switches for one-char commands */
  if (!replacer) {
    while (*c == '/') {
      t = swtch;
      c++;
      while ((*c) && (*c != ' ') && (*c != '/'))
	*t++ = *c++;
      *t = '\0';
      switchnum = switch_find(cmd, upcasestr(swtch));
      if (!switchnum) {
	if (cmd->type & CMD_T_SWITCHES) {
	  if (*swp)
	    strcat(swp, " ");
	  strcat(swp, swtch);
	} else {
	  notify_format(player,
			T("%s doesn't know switch %s."), cmd->name, swtch);
	  command_parse_free_args;
	  return NULL;
	}
      } else {
	SW_SET(sw, switchnum);
      }
    }
  }
  if (!t)
    SW_SET(sw, SWITCH_NONE);
  if (noevtoken)
    SW_SET(sw, SWITCH_NOEVAL);

  /* If we're calling ATTRIB_SET, the switch is the attribute name */
  if (attrib)
    swp = (char *) attrib;
  else if (!*swp)
    swp = NULL;

  strcpy(command2, p);
  if (*p == ' ')
    p++;
  ap = p;

  /* noeval and direct players.
   * If the noeval switch is set:
   *  (1) if we split on =, and an = is present, eval lhs, not rhs
   *  (2) if we split on =, and no = is present, do not eval arg
   *  (3) if we don't split on =, do not eval arg
   * Special case for ATTRIB_SET by a directly connected player:
   * Treat like noeval, except for #2. Eval arg if no =.
   */

  if ((cmd->func == command_atrset) && fromport) {
    /* Special case: eqsplit, noeval of rhs only */
    command_argparse(player, cause, &p, ls, lsa, cmd, 0, 0);
    command_argparse(player, cause, &p, rs, rsa, cmd, 1, 1);
    SW_SET(sw, SWITCH_NOEVAL);	/* Needed for ATTRIB_SET */
  } else {
    noeval = SW_ISSET(sw, SWITCH_NOEVAL) || noevtoken;
    if (cmd->type & CMD_T_EQSPLIT) {
      char *savep = p;
      command_argparse(player, cause, &p, ls, lsa, cmd, 0, noeval);
      if (noeval && !noevtoken && *p) {
	/* oops, we have a right hand side, should have evaluated */
	p = savep;
	command_argparse(player, cause, &p, ls, lsa, cmd, 0, 0);
      }
      command_argparse(player, cause, &p, rs, rsa, cmd, 1, noeval);
    } else {
      command_argparse(player, cause, &p, ls, lsa, cmd, 0, noeval);
    }
  }


  /* Finish setting up commandraw, if we may need it for hooks */
  if (has_hook(&cmd->hooks.ignore) || has_hook(&cmd->hooks.override)) {
    p = command2;
    if (*p && (*p == ' ')) {
      safe_chr(' ', commandraw, &c2);
      p++;
    }
    if (cmd->type & CMD_T_ARGS) {
      int lsa_index;
      if (lsa[1]) {
	safe_str(lsa[1], commandraw, &c2);
	for (lsa_index = 2; lsa[lsa_index]; lsa_index++) {
	  safe_chr(',', commandraw, &c2);
	  safe_str(lsa[lsa_index], commandraw, &c2);
	}
      }
    } else {
      safe_str(ls, commandraw, &c2);
    }
    if (cmd->type & CMD_T_EQSPLIT) {
      safe_chr('=', commandraw, &c2);
      if (cmd->type & CMD_T_RS_ARGS) {
	int rsa_index;
	/* This is counterintuitive, but rsa[]
	 * starts at 1. */
	if (rsa[1]) {
	  safe_str(rsa[1], commandraw, &c2);
	  for (rsa_index = 2; rsa[rsa_index]; rsa_index++) {
	    safe_chr(',', commandraw, &c2);
	    safe_str(rsa[rsa_index], commandraw, &c2);
	  }
	}
      } else {
	safe_str(rs, commandraw, &c2);
      }
#ifdef NEVER
      /* We used to do this, but we're not sure why */
      process_expression(commandraw, &c2, (const char **) &p, player, cause,
			 cause, noevtoken ? PE_NOTHING :
			 ((PE_DEFAULT & ~PE_EVALUATE) |
			  PE_COMMAND_BRACES), PT_DEFAULT, NULL);
#endif
    }
    *c2 = '\0';
  }

  retval = NULL;
  if (cmd->func == NULL) {
    do_rawlog(LT_ERR, T("No command vector on command %s."), cmd->name);
    return NULL;
  } else {
    char *saveregs[NUMQ];
    init_global_regs(saveregs);
    /* If we have a hook/ignore that returns false, we don't do the command */
    if (run_hook(player, cause, &cmd->hooks.ignore, saveregs, 1)) {
      /* If we have a hook/override, we use that instead */
      if (!has_hook(&cmd->hooks.override) ||
	  !one_comm_match(cmd->hooks.override.obj, player,
			  cmd->hooks.override.attrname, commandraw)) {
	/* Otherwise, we do hook/before, the command, and hook/after */
	run_hook(player, cause, &cmd->hooks.before, saveregs, 1);
	cmd->func(cmd, player, cause, sw, string, swp, ap, ls, lsa, rs, rsa);
	run_hook(player, cause, &cmd->hooks.after, saveregs, 0);
      }
      /* Either way, we might log */
      if (cmd->type & CMD_T_LOGARGS)
	do_log(LT_CMD, player, cause, "%s", string);
      else if (cmd->type & CMD_T_LOGNAME)
	do_log(LT_CMD, player, cause, "%s", commandraw);
    } else {
      retval = commandraw;
    }
    free_global_regs("hook.regs", saveregs);
  }

  command_parse_free_args;
  return retval;
}

#undef command_parse_free_args

/** Execute the huh_command when no command is matched.
 * \param player the enactor.
 * \param cause dbref that caused the command to be executed.
 * \param string the input given.
 */
void
generic_command_failure(dbref player, dbref cause, char *string)
{
  COMMAND_INFO *cmd;
  char *saveregs[NUMQ];

  if ((cmd = command_find("HUH_COMMAND"))) {
    if (!(cmd->type & CMD_T_DISABLED)) {
      init_global_regs(saveregs);
      if (run_hook(player, cause, &cmd->hooks.ignore, saveregs, 1)) {
	/* If we have a hook/override, we use that instead */
	if (!has_hook(&cmd->hooks.override) ||
	    !one_comm_match(cmd->hooks.override.obj, player,
			    cmd->hooks.override.attrname, "HUH_COMMAND")) {
	  /* Otherwise, we do hook/before, the command, and hook/after */
	  run_hook(player, cause, &cmd->hooks.before, saveregs, 1);
	  cmd->func(cmd, player, cause, NULL, string, NULL, NULL, string, NULL,
		    NULL, NULL);
	  run_hook(player, cause, &cmd->hooks.after, saveregs, 0);
	}
	/* Either way, we might log */
	if (cmd->type & CMD_T_LOGARGS)
	  do_log(LT_HUH, player, cause, "%s", string);
      }
      free_global_regs("hook.regs", saveregs);
    }
  }
}


/** Add a restriction to a command.
 * Given a command name and a restriction, apply the restriction to the
 * command in addition to whatever its usual restrictions are.
 * This is used by the configuration file startup in conf.c
 * Valid restrictions are:
 * \verbatim
 *   nobody     disable the command
 *   nogagged   can't be used by gagged players
 *   nofixed    can't be used by fixed players
 *   noguest    can't be used by guests
 *   admin      can only be used by royalty or wizards
 *   wizard     can only be used by wizards
 *   god        can only be used by god
 *   noplayer   can't be used by players, just objects/rooms/exits
 *   logargs    log name and arguments when command is run
 *   logname    log just name when command is run
 * \endverbatim
 * Return 1 on success, 0 on failure.
 * \param name name of command to restrict.
 * \param restriction space-separated string of restrictions
 * \retval 1 successfully restricted command.
 * \retval 0 failure (unable to find command name).
 */
int
restrict_command(const char *name, const char *restriction)
{
  COMMAND_INFO *command;
  struct command_perms_t *c;
  char *message;
  int clear;
  FLAG *mask;
  FLAG *powers;
  char *tp;

  if (!name || !*name || !restriction || !*restriction ||
      !(command = command_find(name)))
    return 0;

  if (command->restrict_message) {
    mush_free((Malloc_t) command->restrict_message, "cmd_restrict_message");
    command->restrict_message = NULL;
  }
  message = strchr(restriction, '"');
  if (message) {
    *(message++) = '\0';
    if ((message = trim_space_sep(message, ' ')) && *message)
      command->restrict_message = mush_strdup(message, "cmd_restrict_message");
  }

  while (restriction && *restriction) {
    if ((tp = strchr(restriction, ' ')))
      *tp++ = '\0';

    clear = 0;
    if (*restriction == '!') {
      restriction++;
      clear = 1;
    }

    if (!strcasecmp(restriction, "noplayer")) {
      /* Pfft. And even !noplayer works. */
      clear = !clear;
      restriction += 2;
    }

    /* Gah. I love backwards compatiblity. */
    if (!strcasecmp(restriction, "admin")) {
      FLAG *roy = match_flag("ROYALTY");
      FLAG *wiz = match_flag("WIZARD");
      if (clear && command->flagmask) {
	clear_flag_bitmask(command->flagmask, roy->bitpos);
	clear_flag_bitmask(command->flagmask, wiz->bitpos);
      } else {
	if (!command->flagmask)
	  command->flagmask = new_flag_bitmask("FLAG");
	set_flag_bitmask(command->flagmask, roy->bitpos);
	set_flag_bitmask(command->flagmask, wiz->bitpos);
      }
    } else if ((c = ptab_find(&ptab_command_perms, restriction))) {
      if (clear)
	command->type &= ~c->type;
      else
	command->type |= c->type;
    } else if ((mask = match_flag(restriction))) {
      if (clear && command->flagmask)
	clear_flag_bitmask(command->flagmask, mask->bitpos);
      else {
	if (!command->flagmask)
	  command->flagmask = new_flag_bitmask("FLAG");
	set_flag_bitmask(command->flagmask, mask->bitpos);
      }
    } else if ((powers = match_power(restriction))) {
      if (clear && command->powers)
	clear_flag_bitmask(command->powers, powers->bitpos);
      else {
	if (!command->powers)
	  command->powers = new_flag_bitmask("POWER");
	set_flag_bitmask(command->powers, powers->bitpos);
      }
    }
    restriction = tp;
  }
  return 1;
}

/** Command stub for \@command/add-ed commands.
 * This does nothing more than notify the player
 * with "This command has not been implemented"
 */
COMMAND (cmd_unimplemented) {
  notify(player, "This command has not been implemented");
}

/** Adds a user-added command
 * \verbatim
 * This code implements @command/add, which adds a
 * command with cmd_unimplemented as a stub
 * \endverbatim
 * \param player the enactor
 * \param name the name
 * \param flags CMD_T_* flags
 */
void
do_command_add(dbref player, char *name, int flags)
{
  COMMAND_INFO *command;

  if (!God(player)) {
    notify(player, T("Permission denied."));
    return;
  }
  name = trim_space_sep(name, ' ');
  upcasestr(name);
  command = command_find(name);
  if (!command) {
    if (!ok_command_name(name)) {
      notify(player, T("Bad command name."));
    } else {
      command_add(mush_strdup(name, "command_add"),
		  flags, NULL,
		  0, (flags & CMD_T_NOPARSE ? NULL : "NOEVAL"),
		  cmd_unimplemented);
      notify_format(player, T("Command %s added."), name);
    }
  } else {
    notify_format(player, T("Command %s already exists"), command->name);
  }
}

/** Deletes a user-added command
 * \verbatim
 * This code implements @command/delete, which deletes a
 * command added via @command/add
 * \endverbatim
 * \param player the enactor
 * \param name name of the command to delete
 */
void
do_command_delete(dbref player, char *name)
{
  int acount;
  char alias[BUFFER_LEN];
  COMMAND_INFO *cptr;
  COMMAND_INFO *command;

  if (!God(player)) {
    notify(player, T("Permission denied."));
    return;
  }
  upcasestr(name);
  command = command_find_exact(name);
  if (!command) {
    notify(player, T("No such command."));
    return;
  }
  if (strcasecmp(command->name, name) == 0) {
    /* This is the command, not an alias */
    if (command->func != cmd_unimplemented) {
      notify(player,
	     T
	     ("You can't delete built-in commands. @command/disable instead."));
      return;
    } else {
      acount = 0;
      cptr = ptab_firstentry_new(&ptab_command, alias);
      while (cptr) {
	if (cptr == command) {
	  ptab_delete(&ptab_command, alias);
	  acount++;
	  cptr = ptab_firstentry_new(&ptab_command, alias);
	} else
	  cptr = ptab_nextentry_new(&ptab_command, alias);
      }
      mush_free((Malloc_t) command->name, "command_add");
      mush_free((Malloc_t) command, "command");
      if (acount > 1)
	notify_format(player, T("Removed %s and aliases from command table."),
		      name);
      else
	notify_format(player, T("Removed %s from command table."), name);
    }
  } else {
    /* This is an alias. Just remove it */
    ptab_delete(&ptab_command, name);
    notify_format(player, T("Removed %s from command table."), name);
  }
}

/** Definition of the \@command command.
 * This is the only command which should be defined in this
 * file, because it uses variables from this file, etc.
 */
COMMAND (cmd_command) {
  COMMAND_INFO *command;
  SWITCH_VALUE *sw_val;
  char buff[BUFFER_LEN];
  char *bp = buff;

  if (!arg_left[0]) {
    notify(player, T("You must specify a command."));
    return;
  }
  if (SW_ISSET(sw, SWITCH_ADD)) {
    int flags = CMD_T_ANY;
    flags |= SW_ISSET(sw, SWITCH_NOEVAL) ? CMD_T_NOPARSE : 0;
    flags |= SW_ISSET(sw, SWITCH_RSARGS) ? CMD_T_RS_ARGS : 0;
    flags |= SW_ISSET(sw, SWITCH_LSARGS) ? CMD_T_LS_ARGS : 0;
    flags |= SW_ISSET(sw, SWITCH_LSARGS) ? CMD_T_LS_ARGS : 0;
    flags |= SW_ISSET(sw, SWITCH_EQSPLIT) ? CMD_T_EQSPLIT : 0;
    do_command_add(player, arg_left, flags);
    return;
  }
  if (SW_ISSET(sw, SWITCH_ALIAS)) {
    if (Wizard(player)) {
      if (!ok_command_name(upcasestr(arg_right))) {
	notify(player, "I can't alias a command to that!");
      } else if (!alias_command(arg_left, arg_right)) {
	notify(player, "Unable to set alias.");
      } else {
	if (!SW_ISSET(sw, SWITCH_QUIET))
	  notify(player, "Alias set.");
      }
    } else {
      notify(player, T("Permission denied."));
    }
    return;
  }

  if (SW_ISSET(sw, SWITCH_DELETE)) {
    do_command_delete(player, arg_left);
    return;
  }
  command = command_find(arg_left);
  if (!command) {
    notify(player, T("No such command."));
    return;
  }
  if (Wizard(player)) {
    if (SW_ISSET(sw, SWITCH_ON) || SW_ISSET(sw, SWITCH_ENABLE))
      command->type &= ~CMD_T_DISABLED;
    else if (SW_ISSET(sw, SWITCH_OFF) || SW_ISSET(sw, SWITCH_DISABLE))
      command->type |= CMD_T_DISABLED;

    if (SW_ISSET(sw, SWITCH_RESTRICT)) {
      if (!arg_right || !arg_right[0]) {
	notify(player, T("How do you want to restrict the command?"));
	return;
      }

      if (!restrict_command(arg_left, arg_right))
	notify(player, T("Restrict attempt failed."));
    }

    if ((command->func == cmd_command) && (command->type & CMD_T_DISABLED)) {
      notify(player, T("@command is ALWAYS enabled."));
      command->type &= ~CMD_T_DISABLED;
    }
  }
  if (!SW_ISSET(sw, SWITCH_QUIET)) {
    notify_format(player,
		  "Name       : %s (%s)", command->name,
		  (command->type & CMD_T_DISABLED) ? "Disabled" : "Enabled");
    if ((command->type & CMD_T_ANY) == CMD_T_ANY)
      safe_strl("Any", 3, buff, &bp);
    else {
      buff[0] = '\0';
      if (command->type & CMD_T_ROOM)
	strccat(buff, &bp, "Room");
      if (command->type & CMD_T_THING)
	strccat(buff, &bp, "Thing");
      if (command->type & CMD_T_EXIT)
	strccat(buff, &bp, "Exit");
      if (command->type & CMD_T_PLAYER)
	strccat(buff, &bp, "Player");
    }
    *bp = '\0';
    notify_format(player, "Types      : %s", buff);
    buff[0] = '\0';
    bp = buff;
    if (command->type & CMD_T_SWITCHES)
      strccat(buff, &bp, "Switches");
    if (command->type & CMD_T_NOGAGGED)
      strccat(buff, &bp, "Nogagged");
    if (command->type & CMD_T_NOFIXED)
      strccat(buff, &bp, "Nofixed");
    if (command->type & CMD_T_NOGUEST)
      strccat(buff, &bp, "Noguest");
    if (command->type & CMD_T_EQSPLIT)
      strccat(buff, &bp, "Eqsplit");
    if (command->type & CMD_T_GOD)
      strccat(buff, &bp, "God");
    if (command->type & CMD_T_LOGARGS)
      strccat(buff, &bp, "LogArgs");
    else if (command->type & CMD_T_LOGNAME)
      strccat(buff, &bp, "LogName");
    *bp = '\0';
    notify_format(player, "Restrict   : %s", buff);
    buff[0] = '\0';
    notify(player, show_command_flags(command->flagmask, command->powers));
    bp = buff;
    for (sw_val = switch_list; sw_val->name; sw_val++)
      if (SW_ISSET(command->sw, sw_val->value))
	strccat(buff, &bp, sw_val->name);
    *bp = '\0';
    notify_format(player, "Switches   : %s", buff);
    buff[0] = '\0';
    bp = buff;
    if (command->type & CMD_T_LS_ARGS) {
      if (command->type & CMD_T_LS_SPACE)
	strccat(buff, &bp, "Space-Args");
      else
	strccat(buff, &bp, "Args");
    }
    if (command->type & CMD_T_LS_NOPARSE)
      strccat(buff, &bp, "Noparse");
    if (command->type & CMD_T_EQSPLIT) {
      *bp = '\0';
      notify_format(player, "Leftside   : %s", buff);
      buff[0] = '\0';
      bp = buff;
      if (command->type & CMD_T_RS_ARGS) {
	if (command->type & CMD_T_RS_SPACE)
	  strccat(buff, &bp, "Space-Args");
	else
	  strccat(buff, &bp, "Args");
      }
      if (command->type & CMD_T_RS_NOPARSE)
	strccat(buff, &bp, "Noparse");
      *bp = '\0';
      notify_format(player, "Rightside  : %s", buff);
    } else {
      *bp = '\0';
      notify_format(player, "Arguments  : %s", buff);
    }
    if (Wizard(player)) {
      if (GoodObject(command->hooks.before.obj))
	notify_format(player, "@hook/before: #%d/%s",
		      command->hooks.before.obj,
		      command->hooks.before.attrname);
      if (GoodObject(command->hooks.after.obj))
	notify_format(player, "@hook/after: #%d/%s", command->hooks.after.obj,
		      command->hooks.after.attrname);
      if (GoodObject(command->hooks.ignore.obj))
	notify_format(player, "@hook/ignore: #%d/%s",
		      command->hooks.ignore.obj,
		      command->hooks.ignore.attrname);
      if (GoodObject(command->hooks.override.obj))
	notify_format(player, "@hook/override: #%d/%s",
		      command->hooks.override.obj,
		      command->hooks.override.attrname);
    }
  }
}

/** Display a list of defined commands.
 * This function sends a player the list of commands.
 * \param player the enactor.
 * \param lc if true, list is in lowercase rather than uppercase.
 */
void
do_list_commands(dbref player, int lc)
{
  char *b = list_commands();
  notify_format(player, "Commands: %s", lc ? strlower(b) : b);
}

/** Return a list of defined commands.
 * This function returns a space-separated list of commands as a string.
 */
char *
list_commands(void)
{
  COMMAND_INFO *command;
  const char *ptrs[BUFFER_LEN / 2];
  static char buff[BUFFER_LEN];
  char *bp;
  int nptrs = 0, i;
  command = (COMMAND_INFO *) ptab_firstentry(&ptab_command);
  while (command) {
    ptrs[nptrs] = command->name;
    nptrs++;
    command = (COMMAND_INFO *) ptab_nextentry(&ptab_command);
  }

  do_gensort((dbref) 0, (char **) ptrs, nptrs, ALPHANUM_LIST);
  bp = buff;
  safe_str(ptrs[0], buff, &bp);
  for (i = 1; i < nptrs; i++) {
    if (gencomp
	((dbref) 0, (char *) ptrs[i], (char *) ptrs[i - 1],
	 ALPHANUM_LIST) > 0) {
      safe_chr(' ', buff, &bp);
      safe_str(ptrs[i], buff, &bp);
    }
  }
  *bp = '\0';
  return buff;
}


/* Check command permissions. Return 1 if player can use command,
 * 0 otherwise, and maybe be noisy about it.
 */
static int
command_check(dbref player, COMMAND_INFO *cmd)
{
  int ok;
  char *mess = NULL;
  int check_flags, check_powers;

  /* If disabled, return silently */
  if (cmd->type & CMD_T_DISABLED)
    return 0;
  if ((cmd->type & CMD_T_NOGAGGED) && Gagged(player)) {
    mess = T("You cannot do that while gagged.");
    goto send_error;
  }
  if ((cmd->type & CMD_T_NOFIXED) && Fixed(player)) {
    mess = T("You cannot do that while fixed.");
    goto send_error;
  }
  if ((cmd->type & CMD_T_NOGUEST) && Guest(player)) {
    mess = T("Guests cannot do that.");
    goto send_error;
  }
  if ((cmd->type & CMD_T_GOD) && (!God(player))) {
    mess = T("Only God can do that.");
    goto send_error;
  }
  switch (Typeof(player)) {
  case TYPE_ROOM:
    ok = (cmd->type & CMD_T_ROOM);
    break;
  case TYPE_THING:
    ok = (cmd->type & CMD_T_THING);
    break;
  case TYPE_EXIT:
    ok = (cmd->type & CMD_T_EXIT);
    break;
  case TYPE_PLAYER:
    ok = (cmd->type & CMD_T_PLAYER);
    break;
  default:
    ok = 0;
  }
  if (!ok) {
    mess = T("Permission denied, command is type-restricted.");
    goto send_error;
  }
  /* A command can specify required flags or powers, and if
   * any match, the player is ok to do the command.
   */
  ok = 1;
  check_flags = cmd->flagmask && !null_flagmask("FLAG", cmd->flagmask);
  check_powers = cmd->powers && !null_flagmask("POWER", cmd->powers);

  if (check_flags)
    ok = !!(has_any_flags_by_mask(player, cmd->flagmask));
  if (!ok && check_powers)
    ok = !!(has_any_powers_by_mask(player, cmd->powers));
  if (!ok) {
    mess = T("Permission denied.");
    goto send_error;
  }
  return ok;

send_error:
  if (cmd->restrict_message)
    notify(player, cmd->restrict_message);
  else if (mess)
    notify(player, mess);
  return 0;
}

/** Determine whether a player can use a command.
 * This function checks whether a player can use a command.
 * If the command is disallowed, the player is informed.
 * \param player player whose privileges are checked.
 * \param name name of command.
 * \retval 0 player may not use command.
 * \retval 1 player may use command.
 */
int
command_check_byname(dbref player, const char *name)
{
  COMMAND_INFO *cmd;
  cmd = command_find(name);
  if (!cmd)
    return 0;
  return command_check(player, cmd);
}

static int
has_hook(struct hook_data *hook)
{
  if (!hook || !GoodObject(hook->obj) || IsGarbage(hook->obj)
      || !hook->attrname)
    return 0;
  return 1;
}


/** Run a command hook.
 * This function runs a hook before or after a command execution.
 * \param player the enactor.
 * \param cause dbref that caused command to execute.
 * \param hook pointer to the hook.
 * \param saveregs array to store a copy of the final q-registers.
 * \param save if true, use saveregs to store a ending q-registers.
 * \retval 1 Hook doesn't exist, or evaluates to a non-false value
 * \retval 0 Hook exists and evaluates to a false value
 */
int
run_hook(dbref player, dbref cause, struct hook_data *hook, char *saveregs[],
	 int save)
{
  ATTR *atr;
  char *code;
  const char *cp;
  char buff[BUFFER_LEN], *bp;
  char *origregs[NUMQ];

  if (!has_hook(hook))
    return 1;

  atr = atr_get(hook->obj, hook->attrname);

  if (!atr)
    return 1;

  code = safe_atr_value(atr);
  if (!code)
    return 1;
  add_check("hook.code");

  save_global_regs("run_hook", origregs);
  restore_global_regs("hook.regs", saveregs);

  cp = code;
  bp = buff;

  process_expression(buff, &bp, &cp, hook->obj, cause, player, PE_DEFAULT,
		     PT_DEFAULT, NULL);
  *bp = '\0';

  if (save)
    save_global_regs("hook.regs", saveregs);
  restore_global_regs("run_hook", origregs);

  mush_free(code, "hook.code");
  return parse_boolean(buff);
}

/** Set up or remove a command hook.
 * \verbatim
 * This is the top-level function for @hook. If an object and attribute
 * are given, establishes a hook; if neither are given, removes a hook.
 * \endverbatim
 * \param player the enactor.
 * \param command command to hook.
 * \param obj name of object containing the hook attribute.
 * \param attrname of hook attribute on obj.
 * \param flag type of hook
 */
void
do_hook(dbref player, char *command, char *obj, char *attrname,
	enum hook_type flag)
{
  COMMAND_INFO *cmd;
  struct hook_data *h;

  cmd = command_find(command);
  if (!cmd) {
    notify(player, T("No such command."));
    return;
  }
  if ((cmd->func == cmd_password) || (cmd->func == cmd_newpassword)) {
    notify(player, T("Hooks not allowed with that command."));
    return;
  }

  if (flag == HOOK_BEFORE)
    h = &cmd->hooks.before;
  else if (flag == HOOK_AFTER)
    h = &cmd->hooks.after;
  else if (flag == HOOK_IGNORE)
    h = &cmd->hooks.ignore;
  else if (flag == HOOK_OVERRIDE)
    h = &cmd->hooks.override;
  else {
    notify(player, T("Unknown hook type"));
    return;
  }

  if (!obj && !attrname) {
    notify_format(player, T("Hook removed from %s."), cmd->name);
    h->obj = NOTHING;
    mush_free(h->attrname, "hook.attr");
    h->attrname = NULL;
  } else if (!obj || !*obj || !attrname || !*attrname) {
    notify(player, T("You must give both an object and attribute."));
  } else {
    dbref objdb = match_thing(player, obj);
    if (!GoodObject(objdb)) {
      notify(player, T("Invalid hook object."));
      return;
    }
    h->obj = objdb;
    if (h->attrname)
      mush_free(h->attrname, "hook.attr");
    h->attrname = mush_strdup(strupper(attrname), "hook.attr");
    notify_format(player, T("Hook set for %s"), cmd->name);
  }
}