lpmoo-1.2/etc/
lpmoo-1.2/mudlib/
lpmoo-1.2/mudlib/etc/
lpmoo-1.2/mudlib/include/
lpmoo-1.2/mudlib/include/moo/
lpmoo-1.2/mudlib/lpc/
lpmoo-1.2/mudlib/std/auto/
lpmoo-1.2/mudlib/std/bfuns/
/*
 * NAME:	object.c
 * DESCRIPTION:	generic MOO object
 */

# define DEBUG  0

inherit core	"/std/core";
inherit cache	"/std/cache";
inherit		"/std/string";
inherit		"/std/data";

# if DEBUG
inherit "/std/vartext";
# else
# define var2str(arg)  ""
# endif

# include <objects.h>
# include <moo/data.h>
# include <moo/perms.h>
# include <moo/verbinfo.h>
# include <moo/command.h>
# include <moo/verb.h>
# include <moo/config.h>

# include "object.h"
# include "dbloader.h"

object	parent;		/* parent(this) */
object *children;	/* children(this) */

string	b_name;		/* this.name */
int	b_owner;	/* this.owner */
object	b_location;	/* this.location */
object *b_contents;	/* this.contents */

int	b_flags;	/* object flags */

mapping	properties;	/* all property slots */
			/* ({ owner, perms, value[, cname || 0] }) */

mixed **verbs;		/* this object's defined verbs */
			/* ({ owner, perms, names, prep, object }) */

mixed  *bootstrap;	/* bootstrapping information */

/*
 * NAME:	create()
 * DESCRIPTION:	called by DGD when this object is created
 */
static
void create(void)
{
  core::create();
  cache::reset(CONFIG->query(CF_VERB_CACHE));
}

/*
 * NAME:	init()
 * DESCRIPTION:	initialize this object with parent and owner
 */
void init(object nparent, int nowner)
{
  object ob;

  parent     = nparent;
  children   = ({ });

  b_name     = "";
  b_owner    = nowner;
  b_location = 0;
  b_contents = ({ });
  b_flags    = 0;

  properties = parent ? parent->child_props(nowner) : ([ ]);
  verbs      = ({ });

  if ((ob = MOOOBJ(nowner)) && ob != this_object())
    ob->set_owner_flag();
}

/*
 * NAME:	bootstrap()
 * DESCRIPTION:	called by dbloader to bootstrap (instead of init)
 */
void bootstrap(mixed *descrip)
{
  mixed *v;

  parent     = 0;		/* not known yet */
  children   = ({ });		/* not known yet */

  b_name     = descrip[O_NAME];
  b_owner    = descrip[O_OWNER];

  b_location = 0;		/* not known yet */
  b_contents = ({ });		/* not known yet */
  b_flags    = (descrip[O_FLAGS] & 0xff) | F_OWNER;

  if (b_flags & F_PLAYER)
    global->set_player_flag(this_object(), 1);

  properties = ([ ]);		/* not known yet */
  verbs      = 0;		/* not known yet */

  bootstrap  = descrip;
}

/*
 * NAME:	get_bootstrap_info()
 * DESCRIPTION:	return this object's bootstrapping information
 */
mixed *get_bootstrap_info(void)
{
  return bootstrap;
}

/*
 * NAME:	bootstrap_links()
 * DESCRIPTION:	trace hierarchies and link references
 */
void bootstrap_links(void)
{
  mixed *obdef, *pdefs, *pvals, *info;
  int vc, i, sz, inher;

  if (parent = MOOOBJ(bootstrap[O_PARENT]))
    parent->gain_child(this_object());

  if (b_location = MOOOBJ(bootstrap[O_LOCATION]))
    b_location->gain_content(this_object());

  obdef = bootstrap;
  pdefs = obdef[O_PROPDEFS];
  pvals = obdef[O_PROPVALS];

  if (pvals == 0)
    return;

  vc = inher = 0;
  while (obdef)
    {
      if (pdefs)
	{
	  for (i = 0, sz = sizeof(pdefs); i < sz; ++i, ++vc)
	    {
	      string name, cname;

	      if (inher)
		properties[tolower(pdefs[i])] = ({
		  pvals[vc][P_OWNER],
		  pvals[vc][P_PERMS],
		  pvals[vc][P_VALUE],
		});
	      else
		properties[name = tolower(cname = pdefs[i])] = ({
		  pvals[vc][P_OWNER],
		  pvals[vc][P_PERMS],
		  pvals[vc][P_VALUE],
		  cname == name ? 0 : cname,
		});
	    }
	}

      if (obdef[O_PARENT] != -1)
	{
	  obdef = MOOOBJ(obdef[O_PARENT])->get_bootstrap_info();
	  pdefs = obdef[O_PROPDEFS];
	  inher = 1;
	}
      else
	obdef = 0;
    }

  if (vc != sizeof(pvals))
    error("Inconsistent property defs/values: " + vc + "/" + sizeof(pvals));

  bootstrap[O_PROPVALS] = 0;

  info  = bootstrap[O_VERBDEFS];
  verbs = allocate(sz = sizeof(info));

  for (i = sz; i--; )
    verbs[i] = ({
      info[i][V_OWNER],
      info[i][V_PERMS],
      info[i][V_NAME],
      info[i][V_PREP] >= 0 ? info[i][V_PREP] + 1 : info[i][V_PREP],
      0 /* vobj */,
    });

  bootstrap[O_VERBDEFS] = 0;
}

/*
 * NAME:	bootstrap_cleanup()
 * DESCRIPTION:	erase bootstrapping info
 */
void bootstrap_cleanup(void)
{
  bootstrap = 0;
}

/*
 * NAME:	bootstrap_verb()
 * DESCRIPTION:	install a verb
 */
void bootstrap_verb(int vnum, object vobj)
{
  (verbs[vnum][VERB_OBJECT] = vobj)->ref();
}

/*
 * NAME:	get_flags()
 * DESCRIPTION:	return (standard) b_flags (used by dbsaver)
 */
int get_flags(void)
{
  return b_flags & 0xff;
}

/*
 * NAME:	child_props()
 * DESCRIPTION:	return a mapping of properties for children
 */
mapping child_props(int nowner)
{
  mapping nprops;
  string *keys;
  mixed **data;
  int i;

  nprops = ([ ]);

  keys = map_indices(properties);
  data = map_values(properties);
  for (i = sizeof(keys); i--; )
    {
      mixed *src;

      src = data[i];
      nprops[keys[i]] = ({
	(src[PROP_PERMS] & P_CHOWN) ? nowner : src[PROP_OWNER],
	src[PROP_PERMS],
	STW(0),
      });
    }

  return nprops;
}

/*
 * NAME:	is_player()
 * DESCRIPTION:	return true iff this object is a player
 */
int is_player(void)
{
  return b_flags & F_PLAYER;
}

/*
 * NAME:	is_programmer()
 * DESCRIPTION:	return true iff this object is a programmer
 */
int is_programmer(void)
{
  return b_flags & F_PROGRAMMER;
}

/*
 * NAME:	is_wizard()
 * DESCRIPTION:	return true iff this object is a wizard
 */
int is_wizard(void)
{
  return b_flags & F_WIZARD;
}

/*
 * NAME:	is_owner()
 * DESCRIPTION:	does this object own any object/property/verb besides its own?
 */
int is_owner(void)
{
  return b_flags & F_OWNER;
}

/*
 * NAME:	set_owner_flag()
 * DESCRIPTION:	this object owns an object/property/verb besides its own
 */
void set_owner_flag(void)
{
  b_flags |= F_OWNER;
}

/*
 * NAME:	set_player_flag()
 * DESCRIPTION:	alter the player bit
 */
void set_player_flag(int value)
{
  if (value && ! (b_flags & F_PLAYER))
    {
      b_flags |= F_PLAYER;
      global->set_player_flag(this_object(), 1);
    }
  else if (! value && (b_flags & F_PLAYER))
    {
      b_flags &= ~F_PLAYER;
      global->set_player_flag(this_object(), 0);
    }
}

/*
 * NAME:	get_parent()
 * DESCRIPTION:	return the parent of this object
 */
object get_parent(void)
{
  return parent;
}

/*
 * NAME:	get_children()
 * DESCRIPTION:	return the list of this object's children
 */
object *get_children(void)
{
  return children;
}

/*
 * NAME:	get_ancestors()
 * DESCRIPTION:	return our entire heritage
 */
object *get_ancestors(void)
{
  object ob, *ancestors;

  ob = parent;
  ancestors = ({ });

  while (ob)
    {
      ancestors += ({ ob });
      ob = ob->get_parent();
    }

  return ancestors;
}

/*
 * NAME:	get_prop_names()
 * DESCRIPTION:	return a list of properties defined on _this_ object
 */
string *get_prop_names(void)
{
  string *names, *props;
  mixed *data;
  int i, j, sz;

  names = map_indices(properties);
  data  = map_values(properties);

  props = allocate(sz = sizeof(names));
  for (i = 0, j = -1; i < sz; ++i)
    if (! INHERITED(data[i]))
      props[++j] = names[i];

  return props[.. j];
}

/*
 * NAME:	get_prop_data()
 * DESCRIPTION:	return the internal data for a property
 */
mixed *get_prop_data(string name)
{
  return properties[name];
}

/*
 * NAME:	get_verb_data()
 * DESCRIPTION:	return the internal data for all verbs
 */
mixed *get_verb_data(void)
{
  return verbs;
}

/*
 * NAME:	rec_defined_prop()
 * DESCRIPTION:	return 1 iff this or any descendant defines the property
 */
int rec_defined_prop(string name)
{
  mixed *prop;
  int i;

  if ((prop = properties[name]) && ! INHERITED(prop))
    return 1;

  for (i = sizeof(children); i--; )
    if (children[i]->rec_defined_prop(name))
      return 1;

  return 0;
}

/*
 * NAME:	rec_add_prop()
 * DESCRIPTION:	insert the given property to this object and all descendants
 */
void rec_add_prop(string name, int nowner, int perms)
{
  int i;

  properties[name] = ({
    (perms & P_CHOWN ? b_owner : nowner),
    perms,
    STW(0),
  });

  for (i = sizeof(children); i--; )
    children[i]->rec_add_prop(name, nowner, perms);
}

/*
 * NAME:	rec_del_prop()
 * DESCRIPTION:	remove the indicated property from this and all descendants
 */
void rec_del_prop(string name)
{
  int i;

  properties[name] = 0;

  for (i = sizeof(children); i--; )
    children[i]->rec_del_prop(name);
}

/*
 * NAME:	dump_verb_cache()
 * DESCRIPTION:	return contents of verb cache (DEBUGGING)
 */
mixed dump_verb_cache(void)
{
  return cache::dump();
}

/*
 * NAME:	do_chparent()
 * DESCRIPTION:	atomically change our parent (locked)
 */
static
int do_chparent(object newparent)
{
  object oldparent, common;
  object *oldancestors, *newancestors;
  int i, oldsz, newsz, oldind, newind;

  if (newparent == parent)
    return E_NONE;

  oldancestors = get_ancestors();
  newancestors = newparent ?
    ({ newparent }) + newparent->get_ancestors() : ({ });

  oldsz = sizeof(oldancestors);
  newsz = sizeof(newancestors);

  /* verify that our new parent is not a descendant */

  for (i = newsz; i--; )
    if (newancestors[i] == this_object())
      return E_INVARG;

  /* verify that none of our descendants defines a prop with the same name
     as one of our new ancestors' */

  for (i = newsz; i--; )
    {
      string *props;
      int j;

      props = newancestors[i]->get_prop_names();
      for (j = sizeof(props); j--; )
	if (rec_defined_prop(props[j]))
	  return E_INVARG;
    }

  /* find our common ancestor */

  for (i = 0; i < oldsz; ++i)
    {
      int j;

      for (j = 0; j < newsz; ++j)
	if (oldancestors[i] == newancestors[j])
	  break;

      if (j < newsz)
	{
	  oldind = i;
	  newind = j;
	  common = oldancestors[i];
	  break;
	}
    }

  if (! common)
    {
      oldind = oldsz;
      newind = newsz;
    }

  /* remove all properties defined by oldancestors[.. oldind - 1] */

  for (i = oldind; i--; )
    {
      string *props;
      int j;

      props = oldancestors[i]->get_prop_names();
      for (j = sizeof(props); j--; rec_del_prop(props[j]));
    }

  /* add all properties defined by newancestors[.. newind - 1] */

  for (i = newind; i--; )
    {
      string *props;
      int j;

      props = newancestors[i]->get_prop_names();
      for (j = sizeof(props); j--; )
	{
	  mixed *data;

	  data = newancestors[i]->get_prop_data(props[j]);
	  rec_add_prop(props[j], data[PROP_OWNER], data[PROP_PERMS]);
	}
    }

  if (parent)
    parent->lose_child(this_object());

  if (parent = newparent)
    parent->gain_child(this_object());

  return E_NONE;
}

/*
 * NAME:	chparent()
 * DESCRIPTION:	change our parent
 */
int chparent(object newparent, mixed *info)
{
  if (info &&	/* info == 0 called by recycle() */
      PROGRAMMER(info) != b_owner &&
      ! WIZARDP(info))
    return E_PERM;

  return with_lock("do_chparent", newparent);
}

/*
 * NAME:	lose_child()
 * DESCRIPTION:	a child is being reparented
 */
void lose_child(object ob)
{
  children -= ({ ob });
}

/*
 * NAME:	gain_child()
 * DESCRIPTION:	a child is being reparented to us
 */
void gain_child(object ob)
{
  children += ({ ob });
}

/*
 * NAME:	get_property_data()
 * DESCRIPTION:	return the property mapping (used by dbsaver)
 */
mapping get_property_data(void)
{
  return properties;
}

/*
 * NAME:	get_properties()
 * DESCRIPTION:	return a list of all properties defined on this object
 */
MOOVAL get_properties(mixed *info)
{
  MOOVAL *props;
  string *keys;
  mixed **data;
  int i, j, sz;

  if (! (b_flags & F_READ) &&
      PROGRAMMER(info) != b_owner &&
      ! WIZARDP(info))
    return STW(E_PERM);

  keys  = map_indices(properties);
  data  = map_values(properties);
  props = allocate(sz = sizeof(keys));

  for (i = 0, j = -1; i < sz; ++i)
    {
      mixed *src;
      string name;

      if (! INHERITED(src = data[i]))
	props[++j] = (name = src[PROP_CNAME]) ? STR(name) : STR(keys[i]);
    }

  return LST(props[.. j]);
}

/*
 * NAME:	get_name()
 * DESCRIPTION:	return the builtin name of this object (SAFE)
 */
string get_name(void)
{
  return b_name;
}

/*
 * NAME:	get_owner()
 * DESCRIPTION:	return the builtin owner of this object (SAFE)
 */
int get_owner(void)
{
  return b_owner;
}

/*
 * NAME:	get_location()
 * DESCRIPTION:	return the builtin location of this object (SAFE)
 */
object get_location(void)
{
  return b_location;
}

/*
 * NAME:	get_contents()
 * DESCRIPTION:	return the builtin contents of this object (SAFE)
 */
object *get_contents(void)
{
  return b_contents;
}

/*
 * NAME:	safe_get_property()
 * DESCRIPTION:	return the value of a property regardless (SAFE)
 */
MOOVAL safe_get_property(string name)
{
  mixed *prop;
  MOOVAL value;

  if (! (prop = properties[name]))
    return STW(E_PROPNF);

  value = prop[PROP_VALUE];

  return STWP(value) ?
    parent->safe_get_property(name) : moo_int2ext(value);
}

/*
 * NAME:	safe_set_property()
 * DESCRIPTION:	set the value of a property regardless (SAFE)
 */
int safe_set_property(string name, MOOVAL value)
{
  mixed *prop;

  if (! (prop = properties[name]))
    return E_PROPNF;

  prop[PROP_VALUE] = moo_ext2int(value);

  return E_NONE;
}

/*
 * NAME:	get_property()
 * DESCRIPTION:	return the value of the requested property (MUTABLE)
 */
MOOVAL get_property(string name, mixed *info)
{
  switch (name = tolower(name))
    {
    case "name":
      return STR(b_name);

    case "owner":
      return OBJ(b_owner);

    case "location":
      return b_location ? OBJ_OBJNUM(b_location) : OBJ(-1);

    case "contents":
      return OBJLIST2MOO(b_contents);

    case "programmer":
      return NUM((b_flags & F_PROGRAMMER) != 0);

    case "wizard":
      return NUM((b_flags & F_WIZARD) != 0);

    case "r":
      return NUM((b_flags & F_READ) != 0);

    case "w":
      return NUM((b_flags & F_WRITE) != 0);

    case "f":
      return NUM((b_flags & F_FERTILE) != 0);

    default:
      {
	mixed *prop;
	MOOVAL value;

	if (! (prop = properties[name]))
	  return STW(E_PROPNF);

	if (! (prop[PROP_PERMS] & P_READ) &&
	    PROGRAMMER(info) != prop[PROP_OWNER] &&
	    ! WIZARDP(info))
	  return STW(E_PERM);

	value = prop[PROP_VALUE];

	return STWP(value) ?
	  parent->safe_get_property(name) : moo_int2ext(value);
      }
    }
}

/*
 * NAME:	set_property()
 * DESCRIPTION:	change the specified property
 */
int set_property(string name, MOOVAL value, mixed *info)
{
  switch (name = tolower(name))
    {
    case "name":
      if (! STRP(value))
	return E_TYPE;
      if ((b_flags & F_PLAYER) &&
	  ! WIZARDP(info))
	return E_PERM;

      b_name = STRVAL(value);
      return E_NONE;

    case "owner":
      {
	object ob;

	if (! OBJP(value))
	  return E_TYPE;
	if (! WIZARDP(info))
	  return E_PERM;

	b_owner = OBJVAL(value);
	if ((ob = MOOOBJ(b_owner)) && ob != this_object())
	  ob->set_owner_flag();

      return E_NONE;
      }

    case "location":
    case "contents":
      return E_PERM;

    case "programmer":
      if (! WIZARDP(info))
	return E_PERM;
      if (! (b_flags & F_PLAYER))
	return E_INVARG;

      if (TRUTHOF(value))
	b_flags |= F_PROGRAMMER;
      else
	b_flags &= ~F_PROGRAMMER;
      return E_NONE;

    case "wizard":
      if (! WIZARDP(info))
	return E_PERM;
      if (! (b_flags & F_PLAYER))
	return E_INVARG;

      if (TRUTHOF(value))
	{
	  b_flags |= F_WIZARD;
	  global->log_msg("WIZARDED: #" + (string) OBJNUM(this_object()) +
			  " by programmer #" + (string) PROGRAMMER(info));
	  /* log a traceback? */
	}
      else
	b_flags &= ~F_WIZARD;
      return E_NONE;

    case "r":
      if (PROGRAMMER(info) != b_owner &&
	  ! WIZARDP(info))
	return E_PERM;

      if (TRUTHOF(value))
	b_flags |= F_READ;
      else
	b_flags &= ~F_READ;
      return E_NONE;

    case "w":
      if (PROGRAMMER(info) != b_owner &&
	  ! WIZARDP(info))
	return E_PERM;

      if (TRUTHOF(value))
	b_flags |= F_WRITE;
      else
	b_flags &= ~F_WRITE;
      return E_NONE;

    case "f":
      if (PROGRAMMER(info) != b_owner &&
	  ! WIZARDP(info))
	return E_PERM;

      if (TRUTHOF(value))
	b_flags |= F_FERTILE;
      else
	b_flags &= ~F_FERTILE;
      return E_NONE;

    default:
      {
	mixed *prop;

	if (! (prop = properties[name]))
	  return E_PROPNF;

	if (PROGRAMMER(info) != prop[PROP_OWNER] &&
	    ! (prop[PROP_PERMS] & P_WRITE) &&
	    ! WIZARDP(info))
	  return E_PERM;

	prop[PROP_VALUE] = moo_ext2int(value);

	return E_NONE;
      }
    }
}

/*
 * NAME:	strpropperms2int()
 * DESCRIPTION:	return an int code for a property perms string
 */
private
int strpropperms2int(string perms)
{
  int i, intperms;

  for (i = strlen(perms); i--; )
    {
      switch (perms[i])
	{
	case 'r':
	case 'R':
	  intperms |= P_READ;
	  break;
	case 'w':
	case 'W':
	  intperms |= P_WRITE;
	  break;
	case 'c':
	case 'C':
	  intperms |= P_CHOWN;
	  break;
	default:
	  return -1;
	}
    }

  return intperms;
}

/*
 * NAME:	intpropperms2str()
 * DESCRIPTION:	return a property perms string from an int code
 */
private
string intpropperms2str(int perms)
{
  return ((perms & P_READ)  ? "r" : "") +
         ((perms & P_WRITE) ? "w" : "") +
	 ((perms & P_CHOWN) ? "c" : "");
}

/*
 * NAME:	strverbperms2int()
 * DESCRIPTION:	return an int code for a verb perms string
 */
private
int strverbperms2int(string perms)
{
  int i, intperms;

  for (i = strlen(perms); i--; )
    {
      switch (perms[i])
	{
	case 'r':
	case 'R':
	  intperms |= P_READ;
	  break;
	case 'w':
	case 'W':
	  intperms |= P_WRITE;
	  break;
	case 'x':
	case 'X':
	  intperms |= P_EXECUTE;
	  break;
	case 'd':
	case 'D':
	  intperms |= P_DEBUG;
	  break;
	default:
	  return -1;
	}
    }

  return intperms;
}

/*
 * NAME:	intverbperms2str()
 * DESCRIPTION:	return a verb perms string from an int code
 */
private
string intverbperms2str(int perms)
{
  return ((perms & P_READ)    ? "r" : "") +
         ((perms & P_WRITE)   ? "w" : "") +
	 ((perms & P_EXECUTE) ? "x" : "") +
	 ((perms & P_DEBUG)   ? "d" : "");
}

/*
 * NAME:	get_property_info()
 * DESCRIPTION:	return owner/perms info on property
 */
MOOVAL get_property_info(string name, mixed *info)
{
  mixed *prop;

  if (! (prop = properties[name = tolower(name)]))
    return STW(E_PROPNF);

  if (! (prop[PROP_PERMS] & P_READ) &&
      PROGRAMMER(info) != prop[PROP_OWNER] &&
      ! WIZARDP(info))
    return STW(E_PERM);

  return LST( ({ OBJ(prop[PROP_OWNER]),
		 STR(intpropperms2str(prop[PROP_PERMS])) }) );
}

/*
 * NAME:	set_property_info()
 * DESCRIPTION:	set owner/perms info on a property
 */
int set_property_info(string name, int nowner, string nperms, mixed *info)
{
  int perms;
  mixed *prop;
  object ob;

  if (! (prop = properties[name = tolower(name)]))
    return E_PROPNF;

  if (PROGRAMMER(info) != prop[PROP_OWNER] &&
      ! WIZARDP(info) &&
      ! (prop[PROP_PERMS] & P_WRITE))
    return E_PERM;

  if (nowner != b_owner &&
      ! WIZARDP(info))
    return E_INVARG;

  perms = strpropperms2int(nperms);
  if (perms < 0)
    return E_INVARG;

  prop[PROP_OWNER] = nowner;
  prop[PROP_PERMS] = perms;

  if ((ob = MOOOBJ(nowner)) && ob != this_object())
    ob->set_owner_flag();

  return E_NONE;
}

/*
 * NAME:	builtin_prop()
 * DESCRIPTION:	return 1 iff a property is built-in
 */
private
int builtin_prop(string name)
{
  switch (name)
    {
      case "name":
      case "owner":
      case "location":
      case "contents":
      case "programmer":
      case "wizard":
      case "r":
      case "w":
      case "f":
	return 1;

      default:
	return 0;
    }
}

/*
 * NAME:	do_add_property()
 * DESCRIPTION:	atomically add a new property (locked)
 */
static
int do_add_property(string cname, MOOVAL value,
		    int nowner, string nperms)
{
  string name;
  int perms;
  object ob;

  if (properties[name = tolower(cname)] ||
      (perms = strpropperms2int(nperms)) < 0 ||
      builtin_prop(name) || rec_defined_prop(name))
    return E_INVARG;

  rec_add_prop(name, nowner, perms);
  properties[name] = ({ nowner, perms, value, cname == name ? 0 : cname });

  if ((ob = MOOOBJ(nowner)) &&
      (ob != this_object() || (ob == this_object() && sizeof(children))))
    ob->set_owner_flag();

  return E_NONE;
}

/*
 * NAME:	add_property()
 * DESCRIPTION:	create a new property slot on this object & all descendants
 */
int add_property(string cname, MOOVAL value,
		 int nowner, string nperms, mixed *info)
{
  if (PROGRAMMER(info) != b_owner &&
      ! WIZARDP(info) &&
      ! (b_flags & F_WRITE))
    return E_PERM;

  if (nowner != b_owner &&
      ! WIZARDP(info))
    return E_INVARG;

  return with_lock("do_add_property", cname, value, nowner, nperms);
}

/*
 * NAME:	delete_property()
 * DESCRIPTION:	remove the given property
 */
int delete_property(string name, mixed *info)
{
  mixed prop;

  if (PROGRAMMER(info) != b_owner &&
      ! WIZARDP(info) &&
      ! (b_flags & F_WRITE))
    return E_PERM;

  if (! (prop = properties[name = tolower(name)]) || INHERITED(prop))
    return E_PROPNF;

  with_lock("rec_del_prop", name);

  return E_NONE;
}

/*
 * NAME:	is_clear_property()
 * DESCRIPTION:	return NUM(1) iff property is clear
 */
MOOVAL is_clear_property(string name, mixed *info)
{
  mixed *prop;
  MOOVAL val;

  if (builtin_prop(name = tolower(name)))
    return NUM(0);

  if (! (prop = properties[name]))
    return STW(E_PROPNF);

  if (! (prop[PROP_PERMS] & P_READ) &&
      PROGRAMMER(info) != prop[PROP_OWNER] &&
      ! WIZARDP(info))
    return STW(E_PERM);

  val = prop[PROP_VALUE];

  return STWP(val) ? NUM(1) : NUM(0);
}

/*
 * NAME:	clear_property()
 * DESCRIPTION:	make the indicated property clear
 */
int clear_property(string name, mixed *info)
{
  mixed *prop;

  if (builtin_prop(name = tolower(name)))
    return E_INVARG;

  if (! (prop = properties[name]))
    return E_PROPNF;

  if (PROGRAMMER(info) != prop[PROP_OWNER] &&
      ! WIZARDP(info) &&
      ! (prop[PROP_PERMS] & P_WRITE))
    return E_PERM;

  if (! INHERITED(prop))
    return E_INVARG;

  prop[PROP_VALUE] = STW(0);

  return E_NONE;
}

/*
 * NAME:	get_verbs()
 * DESCRIPTION:	return list of object's verbs
 */
MOOVAL get_verbs(mixed *info)
{
  MOOVAL *list;
  int i;

  if (! (b_flags & F_READ) &&
      PROGRAMMER(info) != b_owner &&
      ! WIZARDP(info))
    return STW(E_PERM);

  for (list = allocate(i = sizeof(verbs)); i--; )
    list[i] = STR(verbs[i][VERB_NAMES]);

  return LST(list);
}

/*
 * NAME:	fertile()
 * DESCRIPTION:	return 1 iff we are fertile to the given programmer
 */
int fertile(mixed *info)
{
  return
    (b_flags & F_FERTILE) ||
    PROGRAMMER(info) == b_owner ||
    WIZARDP(info);
}

/*
 * NAME:	modify_quota()
 * DESCRIPTION:	change this object's ownership_quota property
 */
int modify_quota(int howmuch)
{
  MOOVAL oq;
  int quota;

  oq = safe_get_property("ownership_quota");
  if (! NUMP(oq))
    return E_NONE;

  quota = NUMVAL(oq) + howmuch;

  if (howmuch < 0 && quota < 0)
    return E_QUOTA;

  safe_set_property("ownership_quota", NUM(quota));

  return E_NONE;
}

/*
 * NAME:	do_create_child()
 * DESCRIPTION:	atomically create a child (locked)
 */
static
object do_create_child(int nowner)
{
  object obj;

  obj = global->create_object(this_object(), nowner);
  children += ({ obj });

  return obj;
}

/*
 * NAME:	create_child()
 * DESCRIPTION:	make a child object
 */
MOOVAL create_child(JS_PROTO, int nowner)
{
  object owner_ob, ob;

  JS_BEGIN;

  if (! fertile(info))
    return STW(E_PERM);

  owner_ob = MOOOBJ(nowner);
  if (owner_ob && owner_ob->modify_quota(-1) != E_NONE)
    return STW(E_QUOTA);

  PUSH(OBJ_OBJNUM(ob = with_lock("do_create_child", nowner)));

  JS_PREP(1);
  RET = ob->call_verb(JS_DATA(1), "initialize",
		      verb_vars(info, ob, "initialize"));
  JS_END;

  return POP();

  JS_END;
}

/*
 * NAME:	find_exec_verb()
 * DESCRIPTION:	return a +x verb on this object
 */
static
mixed *find_exec_verb(string name)
{
  int i, sz;

  for (i = 0, sz = sizeof(verbs); i < sz; ++i)
    {
      mixed *verb;

      if (! ((verb = verbs[i])[VERB_PERMS] & P_EXECUTE))
	continue;

      if (! verbname_match(verb[VERB_NAMES], name))
	continue;

      return verb;
    }

  return 0;
}

/*
 * NAME:	find_spec_verb()
 * DESCRIPTION:	return a verb with specific arguments
 */
static
mixed *find_spec_verb(string name, int spec)
{
  string scrap;
  int num, i, sz;

  if (spec == VSPEC_ANY &&
      (sscanf(name, "%d%s", num, scrap) != 2 || strlen(scrap)))
    num = -1;

  for (i = 0, sz = sizeof(verbs); i < sz; ++i)
    {
      mixed *verb;

      verb = verbs[i];

      if (spec == VSPEC_ANY)
	{
	  if (num == i)
	    return verb;
	}
      else
	{
	  int vspec, args;

	  if ((vspec = verb[VERB_PREP]) != VS_ANY &&
	      vspec != VSPEC_PREP(spec))
	    continue;

	  args = verb[VERB_ARGS];

	  if ((vspec = DOBJ(args)) != VS_ANY &&
	      vspec != VSPEC_DOBJ(spec))
	    continue;

	  if ((vspec = IOBJ(args)) != VS_ANY &&
	      vspec != VSPEC_IOBJ(spec))
	    continue;
	}

      if (! verbname_match(verb[VERB_NAMES], name))
	continue;

      return verb;
    }

  return 0;
}

# if 0
/*
 * NAME:	find_local_verb()
 * DESCRIPTION:	return a verb on this object
 */
mixed *find_local_verb(string name, int spec)
{
  string scrap;
  int num, i, sz;

  if (spec == VSPEC_ANY &&
      (sscanf(name, "%d%s", num, scrap) != 2 || strlen(scrap)))
    num = -1;

  for (i = 0, sz = sizeof(verbs); i < sz; ++i)
    {
      mixed *verb;

      if (spec == VSPEC_ANY && num == i)
	return verbs[i];

      verb = verbs[i];

      switch (spec)
	{
	case VSPEC_EXEC:
	  if (verb[VERB_PERMS] & P_EXECUTE)
	    break;
	  else
	    continue;

	case VSPEC_ANY:
	  break;

	default:
	  {
	    int vspec;

	    if ((vspec = verb[VERB_PREP]) != VS_ANY &&
		vspec != VSPEC_PREP(spec))
	      continue;

	    if ((vspec = DOBJ(verb[VERB_ARGS])) != VS_ANY &&
		vspec != VSPEC_DOBJ(spec))
	      continue;

	    if ((vspec = IOBJ(verb[VERB_ARGS])) != VS_ANY &&
		vspec != VSPEC_IOBJ(spec))
	      continue;
	  }
	}

      if (! verbname_match(tolower(verb[VERB_NAMES]), name))
	continue;

      return verb;
    }

  return 0;
}
# endif

/*
 * NAME:	cache_miss()
 * DESCRIPTION:	find a verb the hard way
 */
static
mixed cache_miss(string key)
{
  return find_exec_verb(key);
}

/*
 * NAME:	find_verb()
 * DESCRIPTION:	return an (inherited) +x verb
 */
mixed *find_verb(string name)
{
  mixed *slot;

  slot = cache::fetch(name);

  return slot ? ({ this_object(), slot }) :
    (parent ? parent->find_verb(name) : 0);
}

/*
 * NAME:	find_command()
 * DESCRIPTION:	return an (inherited) verb with specific arguments
 */
mixed *find_command(string name, int spec)
{
  mixed *slot;

  slot = find_spec_verb(name, spec);

  return slot ? ({ this_object(), slot }) :
    (parent ? parent->find_command(name, spec) : 0);
}

/*
 * NAME:	get_verb_info()
 * DESCRIPTION:	return owner, perms, and names for a verb
 */
MOOVAL get_verb_info(string name, mixed *info)
{
  mixed *verb;

  if (! (verb = find_spec_verb(name, VSPEC_ANY)))
    return STW(E_VERBNF);

  if (! (verb[VERB_PERMS] & P_READ) &&
      PROGRAMMER(info) != verb[VERB_OWNER] &&
      ! WIZARDP(info))
    return STW(E_PERM);

  return LST( ({ OBJ(verb[VERB_OWNER]),
		 STR(intverbperms2str(verb[VERB_PERMS] & VP_PERMMASK)),
		 STR(verb[VERB_NAMES]),
	      }) );
}

/*
 * NAME:	set_verb_info()
 * DESCRIPTION:	change verb owner, perms, and names
 */
int set_verb_info(string name,
		  int nowner, string nperms, string nnames,
		  mixed *info)
{
  mixed *verb;
  int perms;
  object ob;

  if (! (verb = find_spec_verb(name, VSPEC_ANY)))
    return E_VERBNF;

  if (PROGRAMMER(info) != verb[VERB_OWNER] &&
      ! WIZARDP(info) &&
      ! (verb[VERB_PERMS] & P_WRITE))
    return E_PERM;

  if (nowner != verb[VERB_OWNER] &&
      ! WIZARDP(info))
    return E_PERM;

  if ((perms = strverbperms2int(nperms)) < 0)
    return E_INVARG;

  verb[VERB_OWNER] = nowner;
  verb[VERB_PERMS] = (verb[VERB_PERMS] & ~VP_PERMMASK) | perms;
  verb[VERB_NAMES] = nnames;

  cache::reset();

  if ((ob = MOOOBJ(nowner)) && ob != this_object())
    ob->set_owner_flag();

  return E_NONE;
}

/*
 * NAME:	get_verb_args()
 * DESCRIPTION:	return dobj, prep, and iobj for a verb
 */
MOOVAL get_verb_args(string name, mixed *info)
{
  mixed *verb;

  if (! (verb = find_spec_verb(name, VSPEC_ANY)))
    return STW(E_VERBNF);

  if (! (verb[VERB_PERMS] & P_READ) &&
      PROGRAMMER(info) != verb[VERB_OWNER] &&
      ! WIZARDP(info))
    return STW(E_PERM);

  return LST( ({ STR(global->vs_name(DOBJ(verb[VERB_ARGS]))),
		 STR(global->prep_name(verb[VERB_PREP])),
		 STR(global->vs_name(IOBJ(verb[VERB_ARGS]))),
	      }) );
}

/*
 * NAME:	set_verb_args()
 * DESCRIPTION:	change verb arguments
 */
int set_verb_args(string name,
		  string dobj, string prep, string iobj,
		  mixed *info)
{
  mixed *verb;
  int dobjc, prepc, iobjc;

  if (! (verb = find_spec_verb(name, VSPEC_ANY)))
    return E_VERBNF;

  if (PROGRAMMER(info) != verb[VERB_OWNER] &&
      ! (verb[VERB_PERMS] & P_WRITE) &&
      ! WIZARDP(info))
    return E_PERM;

  if (! (dobjc = global->vs_code(dobj)) ||
      ! (prepc = global->prep_code(prep)) ||
      ! (iobjc = global->vs_code(iobj)))
    return E_INVARG;

  verb[VERB_ARGS] = (verb[VERB_ARGS] & ~(VP_DOBJMASK | VP_IOBJMASK)) |
    SDOBJ(dobjc) | SIOBJ(iobjc);
  verb[VERB_PREP] = prepc;

  cache::reset();

  return E_NONE;
}

/*
 * NAME:	add_verb()
 * DESCRIPTION:	create a new verb
 */
int add_verb(int nowner, string nperms, string names,
	     string dobj, string prep, string iobj,
	     mixed *info)
{
  mixed *verb;
  int perms, dobjc, prepc, iobjc;
  object ob;

  if (PROGRAMMER(info) != b_owner &&
      ! WIZARDP(info) &&
      ! (b_flags & F_WRITE))
    return E_PERM;

  if (PROGRAMMER(info) != nowner &&
      ! WIZARDP(info))
    return E_PERM;

  if ((perms = strverbperms2int(nperms)) < 0)
    return E_INVARG;

  if (! (dobjc = global->vs_code(dobj)) ||
      ! (prepc = global->prep_code(prep)) ||
      ! (iobjc = global->vs_code(iobj)))
    return E_INVARG;

  verb = ({ nowner, perms | SDOBJ(dobjc) | SIOBJ(iobjc),
	    names, prepc, 0 /* object */ });

  verbs += ({ verb });
  cache::reset();

  if ((ob = MOOOBJ(nowner)) && ob != this_object())
    ob->set_owner_flag();

  return E_NONE;
}

/*
 * NAME:	del_verb()
 * DESCRIPTION:	remove a verb object
 */
private
void del_verb(mixed *verb)
{
  object vobj;

  if (vobj = verb[VERB_OBJECT])
    vobj->del();
}

/*
 * NAME:	delete_verb()
 * DESCRIPTION:	remove a verb
 */
int delete_verb(string name, mixed *info)
{
  mixed *verb;

  if (! (verb = find_spec_verb(name, VSPEC_ANY)))
    return E_VERBNF;

  if (PROGRAMMER(info) != b_owner &&
      ! (b_flags & F_WRITE) &&
      ! WIZARDP(info))
    return E_PERM;

  del_verb(verb);

  verbs -= ({ verb });
  cache::reset();

  return E_NONE;
}

/*
 * NAME:	set_verb_code()
 * DESCRIPTION:	receive and compile new code for a verb
 */
MOOVAL set_verb_code(string name, string *code, mixed *info)
{
  mixed *verb, *ast;

  if (! (verb = find_spec_verb(name, VSPEC_ANY)))
    return STW(E_VERBNF);

  if (PROGRAMMER(info) != verb[VERB_OWNER] &&
      ! WIZARDP(info) &&
      ! (verb[VERB_PERMS] & P_WRITE))
    return STW(E_PERM);

  ast = parser->main(implode(code, "\n"));

  if (! ast[0])
    return STRLIST2MOO(ast[1]);

  del_verb(verb);
  (verb[VERB_OBJECT] = global->compile_verb(ast[1], 1))->ref();

  return LST(LNEW());
}

/*
 * NAME:	get_verb_code()
 * DESCRIPTION:	return (pretty-printed) MOO code for a verb
 */
MOOVAL get_verb_code(string name, int full_paren, int indent, mixed *info)
{
  mixed *verb;
  object vobj;

  if (! (verb = find_spec_verb(name, VSPEC_ANY)))
    return STW(E_VERBNF);

  if (! (verb[VERB_PERMS] & P_READ) &&
      PROGRAMMER(info) != verb[VERB_OWNER] &&
      ! WIZARDP(info))
    return STW(E_PERM);

  if (! (vobj = verb[VERB_OBJECT]))
    return LST(LNEW());

  return STRLIST2MOO(vobj->get_source(full_paren, indent));
}

/*
 * NAME:	get_disassembled_code()
 * DESCRIPTION:	(don't) return disassembled code for a verb
 */
MOOVAL get_disassembled_code(string name, mixed *info)
{
  mixed *verb;

  if (! (verb = find_spec_verb(name, VSPEC_ANY)))
    return STW(E_VERBNF);

  if (! (verb[VERB_PERMS] & P_READ) &&
      PROGRAMMER(info) != verb[VERB_OWNER] &&
      ! WIZARDP(info))
    return STW(E_PERM);

  return LST( ({ STR("Sorry, disassemble() not supported in LPMOO.") }) );
}

/*
 * NAME:	get_verb_obj()
 * DESCRIPTION:	return the object for a verb, compiling if necessary
 */
object get_verb_obj(string name)
{
  mixed *verb;

  if (! (verb = find_spec_verb(name, VSPEC_ANY)) ||
      UNPROGRAMMED(verb))
    return 0;

  return verb[VERB_OBJECT];
}

/*
 * NAME:	command()
 * DESCRIPTION:	call a verb as a command
 */
int command(string name, string realname,
	    mixed *parsed, object player, object this)
{
  mixed *verb, *info;
  int task_id, player_id, this_id, dobj, iobj, flags, owner;
  object vobj, host;

  if (! this)
    this = this_object();
  this_id = OBJNUM(this);

  if (parsed[C_DOBJ] == MATCH_NOTHING)
    dobj = VS_NONE;
  else if (parsed[C_DOBJ] == this_id)
    dobj = VS_THIS;
  else
    dobj = VS_ANY;

  if (parsed[C_IOBJ] == MATCH_NOTHING)
    iobj = VS_NONE;
  else if (parsed[C_IOBJ] == this_id)
    iobj = VS_THIS;
  else
    iobj = VS_ANY;

  if (! (verb = find_command(name, VSPEC(parsed[C_PREP], dobj, iobj))))
    return 0;

  host = verb[0];
  if (UNPROGRAMMED(verb = verb[1]))
    return parent ?
      parent->command(name, realname, parsed, player, this) : 0;

  task_id   = global->take_task();
  player_id = OBJNUM(player);
  owner     = verb[VERB_OWNER];

  info = ({ ((verb[VERB_PERMS] & P_DEBUG) ? IF_DEBUG : 0) |
	    (wizardp(owner) ? IF_WIZARD : 0),
	    owner,
	    ({ OBJ(player_id),			/* player */
	       OBJ(this_id),			/* this */
	       OBJ(player_id),			/* caller */
	       STRLIST2MOO(parsed[C_ARGS]),	/* args */
	       STR(parsed[C_ARGSTR]),		/* argstr */
	       STR(realname),			/* verb */
	       OBJ(parsed[C_DOBJ]),		/* dobj */
	       STR(parsed[C_DOBJSTR]),		/* dobjstr */
	       STR(parsed[C_PREPSTR]),		/* prepstr */
	       OBJ(parsed[C_IOBJ]),		/* iobj */
	       STR(parsed[C_IOBJSTR]),		/* iobjstr */
	       STD_VARS }), 0,
	    realname, this_id, player_id, task_id, 0,
	    vobj = verb[VERB_OBJECT], host, global->get_max_depth(), 0 });

  vobj->main(JS_INIT_I(info));
  return 1;
}

/*
 * NAME:	call_verb()
 * DESCRIPTION:	call a verb in this object
 */
MOOVAL call_verb(JS_PROTO, string name, MOOVAL *vars)
{
  mixed *verb;
  int task_id, depth, owner, flags;
  object vobj, host;

  JS_BEGIN;

  if (! (verb = find_verb(name)))
    return STW(E_VERBNF);

  host = verb[0];
  if (UNPROGRAMMED(verb = verb[1]))
    return STW(E_VERBNF);

  if (info)
    {
      if (! (depth = info[I_DEPTH] - 1))
	return STW(E_MAXREC);

      task_id = info[I_TASKID];
      flags   = info[I_FLAGS] & IF_FROMLPC;

      info[I_LINENO] = info[I_VERBOBJ]->get_lineno();
    }
  else
    {
      depth   = global->get_max_depth();
      task_id = global->take_task();
    }

  owner = verb[VERB_OWNER];

  info = ({ flags | ((verb[VERB_PERMS] & P_DEBUG) ? IF_DEBUG : 0) |
	    (wizardp(owner) ? IF_WIZARD : 0),
					/* flags */
	    owner,			/* task perms */
	    vars,			/* (initial) vars */
	    0,				/* vardefs */
	    name,			/* verb name */
	    OBJVAL(vars[V_THIS]),	/* initial `this' */
	    OBJVAL(vars[V_PLAYER]),	/* initial `player' */
	    task_id,			/* task id */
	    0,				/* line number */
	    vobj = verb[VERB_OBJECT],	/* verb object */
	    host,			/* host object */
	    depth,			/* recursion depth */
	    info,			/* previous info */
	 });

  JS_PREP(1);
  RET = vobj->main(JS_DATA(1));
  JS_END;

  return RET;

  JS_END;
}

/*
 * NAME:	lose_content()
 * DESCRIPTION:	remove an object from contents list
 */
void lose_content(object ob)
{
  b_contents -= ({ ob });
}

/*
 * NAME:	gain_content()
 * DESCRIPTION:	add an object to contents list
 */
void gain_content(object ob)
{
  b_contents += ({ ob });
}

/*
 * NAME:	do_move()
 * DESCRIPTION:	atomically transfer this object to another location (locked)
 */
static
object do_move(object where)
{
  object oldwhere;

  if (oldwhere = b_location)
    oldwhere->lose_content(this_object());

  if (b_location = where)
    where->gain_content(this_object());

  return oldwhere;
}

/*
 * NAME:	move()
 * DESCRIPTION:	go somewhere
 */
int move(JS_PROTO, object where)
{
  object obj;

  JS_BEGIN;

  if (PROGRAMMER(info) != b_owner &&
      ! WIZARDP(info))
    return E_PERM;

  if (where)
    {
      JS_PREP(1);
      RET = where->call_verb(JS_DATA(1), "accept",
			     verb_vars(info, where, "accept",
				       OBJ_OBJNUM(this_object())));
      JS_END;

      if (STWP(RET) && STWVAL(RET) == E_MAXREC)
	return E_MAXREC;

      if (! WIZARDP(info) && ! TRUTHOF(RET))
	return E_NACC;
    }

  if (where == b_location)
    return E_NONE;

  for (obj = where; obj; obj = obj->get_location())
    if (this_object() == obj)
      return E_RECMOVE;

  if (obj = with_lock("do_move", where))
    {
      JS_PREP(2);
      RET = obj->call_verb(JS_DATA(2), "exitfunc",
			   verb_vars(info, obj, "exitfunc",
				     OBJ_OBJNUM(this_object())));
      JS_END;

      if (STWP(RET) && STWVAL(RET) == E_MAXREC)
	return E_MAXREC;
    }

  if (where && b_location == where)
    {
      JS_PREP(3);
      RET = where->call_verb(JS_DATA(3), "enterfunc",
			     verb_vars(info, where, "enterfunc",
				       OBJ_OBJNUM(this_object())));
      JS_END;

      if (STWP(RET) && STWVAL(RET) == E_MAXREC)
	return E_MAXREC;
    }

  return E_NONE;

  JS_END;
}

/*
 * NAME:	renumber_owner()
 * DESCRIPTION:	change the owner of this object or verbs or properties
 */
void renumber_owner(int old, int new)
{
  mixed *props;
  int i;

  if (b_owner == old)
    b_owner = new;

  for (i = sizeof(verbs); i--; )
    if (verbs[i][VERB_OWNER] == old)
      verbs[i][VERB_OWNER] = new;

  props = map_values(properties);
  for (i = sizeof(props); i--; )
    if (props[i][PROP_OWNER] == old)
      props[i][PROP_OWNER] = new;
}

/*
 * NAME:	recycle()
 * DESCRIPTION:	make this object irrevocably go away
 */
int recycle(JS_PROTO)
{
  int i;
  object owner_ob;

  JS_BEGIN;

  if (PROGRAMMER(info) != b_owner &&
      ! WIZARDP(info))
    return E_PERM;

  if (b_flags & F_RECYCLING)	/* already recycling */
    return E_NONE;

  b_flags |= F_RECYCLING;

  JS_PREP(1);
  RET = call_verb(JS_DATA(1), "recycle",
		  verb_vars(info, this_object(), "recycle"));
  JS_END;

  while (sizeof(children))
    if (children[0]->chparent(parent, 0) != E_NONE)
      error("Error during reparent of child");

  if (chparent(0, 0) != E_NONE)
    error("Error during reparent");

  while (sizeof(b_contents))
    {
      JS_PREP(2);
      RET = b_contents[0]->move(JS_DATA(2), 0);
      JS_END;

      if (RET != E_NONE)
	error("Error during move of content");
    }

  JS_PREP(3);
  RET = move(JS_DATA(3), 0);
  JS_END;

  if (RET != E_NONE)
    error("Error during move");

  for (i = sizeof(verbs); i--; del_verb(verbs[i]));

  if (owner_ob = MOOOBJ(b_owner))
    owner_ob->modify_quota(1);

  global->recycle(this_object());
  destruct_object(this_object());

  return E_NONE;
  JS_END;
}