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 set.c
 *
 * \brief PennMUSH commands that set parameters.
 *
 *
 */

#include "copyrite.h"
#include "config.h"
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#ifdef I_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif
#ifdef I_SYS_TYPES
#include <sys/types.h>
#endif
#include <stdlib.h>

#include "conf.h"
#include "externs.h"
#include "mushdb.h"
#include "match.h"
#include "attrib.h"
#include "ansi.h"
#include "command.h"
#include "mymalloc.h"
#include "flags.h"
#include "dbdefs.h"
#include "lock.h"
#include "log.h"
#include "game.h"
#include "confmagic.h"

static int chown_ok(dbref player, dbref thing, dbref newowner);
void do_attrib_flags
  (dbref player, const char *obj, const char *atrname, const char *flag);
static int af_helper(dbref player, dbref thing, dbref parent,
		     char const *pattern, ATTR *atr, void *args);
static int gedit_helper(dbref player, dbref thing, dbref parent,
			char const *pattern, ATTR *atr, void *args);
static int wipe_helper(dbref player, dbref thing, dbref parent,
		       char const *pattern, ATTR *atr, void *args);
static void copy_attrib_flags(dbref player, dbref target, ATTR *atr, int flags);


/** Rename something.
 * \verbatim
 * This implements @name.
 * \endverbatim
 * \param player the enactor.
 * \param name current name of object to rename.
 * \param newname new name for object.
 */
void
do_name(dbref player, const char *name, char *newname)
{
  dbref thing;
  ATTR *a = NULL;
  char alias[BUFFER_LEN];
  char *password;
  char *myenv[10];
  int i;

  newname = trim_space_sep(newname, ' ');

  if ((thing = match_controlled(player, name)) != NOTHING) {
    /* check for bad name */
    if ((*newname == '\0') || strchr(newname, '[')) {
      notify(player, T("Give it what new name?"));
      return;
    }
    /* check for renaming a player */
    if (IsPlayer(thing)) {
      if (PLAYER_NAME_SPACES) {
	if (*newname == '\"') {
	  for (; *newname && ((*newname == '\"')
			      || isspace((unsigned char) *newname));
	       newname++) ;
	  password = newname;
	  while (*password && (*password != '\"')) {
	    while (*password && (*password != '\"'))
	      password++;
	    if (*password == '\"') {
	      *password++ = '\0';
	      while (*password && isspace((unsigned char) *password))
		password++;
	      break;
	    }
	  }
	} else {
	  password = newname;
	  while (*password && !isspace((unsigned char) *password))
	    password++;
	  if (*password) {
	    *password++ = '\0';
	    while (*password && isspace((unsigned char) *password))
	      password++;
	  }
	}
      } else {

	/* split off password */
	for (password = newname + strlen(newname) - 1;
	     *password && !isspace((unsigned char) *password); password--) ;
	for (; *password && isspace((unsigned char) *password); password--) ;
	/* eat whitespace */
	if (*password) {
	  *++password = '\0';	/* terminate name */
	  password++;
	  while (*password && isspace((unsigned char) *password))
	    password++;
	}
      }
      a = atr_get_noparent(thing, "ALIAS");
      if (a)
	strcpy(alias, atr_value(a));
      if (strcasecmp(newname, Name(thing))
	  && !(a && !strcasecmp(newname, alias))	/* Swap alias, name */
	  &&!ok_player_name(newname, thing)) {
	/* strcasecmp allows changing foo to Foo, etc. */
	notify(player, T("You can't give a player that name."));
	return;
      }
      if (a && strcasecmp(alias, newname)) {
	a = NULL;
      }
      /* everything ok, notify */
      do_log(LT_CONN, 0, 0, T("Name change by %s(#%d) to %s"),
	     Name(thing), thing, newname);
      if (Suspect(thing))
	flag_broadcast("WIZARD", 0,
		       T("Broadcast: Suspect %s changed name to %s."),
		       Name(thing), newname);
      /* everything ok, we can fall through to change the name */
    } else {
      if (!ok_name(newname)) {
	notify(player, T("That is not a reasonable name."));
	return;
      }
    }

    /* everything ok, change the name */
    myenv[0] = (char *) mush_malloc(BUFFER_LEN, "string");
    myenv[1] = (char *) mush_malloc(BUFFER_LEN, "string");
    strncpy(myenv[0], Name(thing), BUFFER_LEN - 1);
    myenv[0][BUFFER_LEN - 1] = '\0';
    strcpy(myenv[1], newname);
    for (i = 2; i < 10; i++)
      myenv[i] = NULL;

    if (IsPlayer(thing) && !a)
      delete_player(thing, NULL);
    else if (a)
      atr_add(thing, "ALIAS", Name(thing), player, 0);

    set_name(thing, newname);

    if (IsPlayer(thing) && !a)
      add_player(thing, NULL);

    if (!AreQuiet(player, thing))
      notify(player, T("Name set."));
    real_did_it(player, thing, NULL, NULL, "ONAME", NULL, "ANAME", NOTHING,
		myenv, NA_INTER_PRESENCE);
    mush_free(myenv[0], "string");
    mush_free(myenv[1], "string");
  }
}

/** Change an object's owner.
 * \verbatim
 * This implements @chown.
 * \endverbatim
 * \param player the enactor.
 * \param name name of object to change owner of.
 * \param newobj name of new owner for object.
 * \param preserve if 1, preserve privileges and don't halt the object.
 */
void
do_chown(dbref player, const char *name, const char *newobj, int preserve)
{
  dbref thing;
  dbref newowner = NOTHING;
  char *sp;
  long match_flags = MAT_POSSESSION | MAT_HERE | MAT_EXIT | MAT_ABSOLUTE;


  /* check for '@chown <object>/<atr>=<player>'  */
  sp = strchr(name, '/');
  if (sp) {
    do_atrchown(player, name, newobj);
    return;
  }
  if (Wizard(player))
    match_flags |= MAT_PLAYER;

  if ((thing = noisy_match_result(player, name, TYPE_THING, match_flags))
      == NOTHING)
    return;

  if (!*newobj || !strcasecmp(newobj, "me")) {
    newowner = player;
  } else {
    if ((newowner = lookup_player(newobj)) == NOTHING) {
      notify(player, T("I couldn't find that player."));
      return;
    }
  }

  if (IsPlayer(thing) && !God(player)) {
    notify(player, T("Players always own themselves."));
    return;
  }
  /* Permissions checking */
  if (!chown_ok(player, thing, newowner)) {
    notify(player, T("Permission denied."));
    return;
  }
  if (IsThing(thing) && !Hasprivs(player) &&
      !(GoodObject(Location(thing)) && (Location(thing) == player))) {
    notify(player, T("You must carry the object to @chown it."));
    return;
  }
  if (preserve && !Wizard(player)) {
    notify(player, T("You cannot @CHOWN/PRESERVE. Use normal @CHOWN."));
    return;
  }
  /* chowns to the zone master don't count towards fees */
  if (!ZMaster(newowner)) {
    /* Debit the owner-to-be */
    if (!can_pay_fees(newowner, Pennies(thing))) {
      /* not enough money or quota */
      if (newowner != player)
	notify(player,
	       T
	       ("That player doesn't have enough money or quota to receive that object."));
      return;
    }
    /* Credit the current owner */
    giveto(Owner(thing), Pennies(thing));
    change_quota(Owner(thing), QUOTA_COST);
  }
  chown_object(player, thing, newowner, preserve);
  notify(player, T("Owner changed."));
}

static int
chown_ok(dbref player, dbref thing, dbref newowner)
{
  /* Cant' touch garbage */
  if (IsGarbage(thing))
    return 0;

  /* Wizards can do it all */
  if (Wizard(player))
    return 1;

  /* In order for non-wiz player to @chown thing to newowner,
   * player must control newowner or newowner must be a Zone Master
   * and player must pass its zone lock.
   *
   * In addition, one of the following must apply:
   *   1.  player owns thing, or
   *   2.  player controls Owner(thing), newowner is a zone master,
   *       and Owner(thing) passes newowner's zone-lock, or
   *   3.  thing is CHOWN_OK, and player holds thing if it's an object.
   *
   * The third condition is syntactic sugar to handle the situation
   * where Joe owns Box, an ordinary object, and Tool, an inherit object, 
   * and ZMP, a Zone Master Player, is zone-locked to =tool.
   * In this case, if Joe doesn't pass ZMP's lock, we don't want
   *   Joe to be able to @fo Tool=@chown Box=ZMP
   */

  /* Does player control newowner, or is newowner a Zone Master and player
   * passes the lock?
   */
  if (!(controls(player, newowner) ||
	(ZMaster(newowner) && eval_lock(player, newowner, Zone_Lock))))
    return 0;

  /* Target player is legitimate. Does player control the object? */
  if (Owns(player, thing))
    return 1;

  if (controls(player, Owner(thing)) &&
      ZMaster(newowner) && eval_lock(Owner(thing), newowner, Zone_Lock))
    return 1;

  if (ChownOk(thing) && (!IsThing(thing) || (Location(thing) == player)))
    return 1;

  return 0;
}


/** Actually change the ownership of something, and fix bits.
 * \param player the enactor.
 * \param thing object to change ownership of.
 * \param newowner new owner for thing.
 * \param preserve if 1, preserve privileges and don't halt.
 */
void
chown_object(dbref player, dbref thing, dbref newowner, int preserve)
{
  (void) undestroy(player, thing);
  if (God(player)) {
    Owner(thing) = newowner;
  } else {
    Owner(thing) = Owner(newowner);
  }
  /* Don't allow circular zones */
  Zone(thing) = NOTHING;
  if (GoodObject(Zone(newowner))) {
    dbref tmp;
    int ok_to_zone = 1;
    int zone_depth = MAX_ZONES;
    for (tmp = Zone(Zone(newowner)); GoodObject(tmp); tmp = Zone(tmp)) {
      if (tmp == thing) {
	notify(player, T("Circular zone broken."));
	ok_to_zone = 0;
	break;
      }
      if (tmp == Zone(tmp))	/* Ran into an object zoned to itself */
	break;
      zone_depth--;
      if (!zone_depth) {
	ok_to_zone = 0;
	notify(player, T("Overly deep zone chain broken."));
	break;
      }
    }
    if (ok_to_zone)
      Zone(thing) = Zone(newowner);
  }
  clear_flag_internal(thing, "CHOWN_OK");
  if (!preserve || !Wizard(player)) {
    clear_flag_internal(thing, "WIZARD");
    clear_flag_internal(thing, "ROYALTY");
    clear_flag_internal(thing, "TRUST");
    set_flag_internal(thing, "HALT");
    destroy_flag_bitmask(Powers(thing));
    Powers(thing) = new_flag_bitmask("POWER");
    do_halt(thing, "", thing);
  } else {
    if ((newowner != player) && Wizard(thing) && !God(player)) {
      notify_format(player,
		    T
		    ("Warning: WIZ flag reset on #%d because @CHOWN/PRESERVE is to a third party."),
		    thing);
      clear_flag_internal(thing, "WIZARD");
    }
    if (!null_flagmask("POWER", Powers(thing)) || Wizard(thing) ||
	Royalty(thing) || Inherit(thing))
      notify_format(player,
		    T
		    ("Warning: @CHOWN/PRESERVE on a object (#%d) with WIZ, ROY, INHERIT, or @power privileges."),
		    thing);
  }
}


/** Change an object's zone.
 * \verbatim
 * This implements @chzone.
 * \endverbatim
 * \param player the enactor.
 * \param name name of the object to change zone of.
 * \param newobj name of new ZMO.
 * \param noisy if 1, notify player about success and failure.
 * \retval 0 failed to change zone.
 * \retval 1 successfully changed zone.
 */
int
do_chzone(dbref player, char const *name, char const *newobj, int noisy)
{
  dbref thing;
  dbref zone;

  if ((thing = noisy_match_result(player, name, NOTYPE, MAT_NEARBY)) == NOTHING)
    return 0;

  if (!newobj || !*newobj || !strcasecmp(newobj, "none"))
    zone = NOTHING;
  else {
    if ((zone = noisy_match_result(player, newobj, NOTYPE, MAT_EVERYTHING))
	== NOTHING)
      return 0;
  }

  if (Zone(thing) == zone) {
    if (noisy)
      notify(player, T("That object is already in that zone."));
    return 0;
  }

  /* we do use ownership instead of control as a criterion because
   * we only want the owner to be able to rezone the object. Also,
   * this allows players to @chzone themselves to an object they own.
   */
  if (!(God(player) || (!God(thing) && Wizard(player)) || Owns(player, thing))) {
    if (noisy)
      notify(player, T("You don't have the power to shift reality."));
    return 0;
  }
  /* a player may change an object's zone to:
   * 1.  NOTHING 
   * 2.  an object he owns
   * 3.  an object with a chzone-lock that the player passes.
   * Note that an object with no chzone-lock isn't valid
   */
  if (!(Wizard(player) || (zone == NOTHING) || Owns(player, zone) ||
	((getlock(zone, Chzone_Lock) != TRUE_BOOLEXP) &&
	 eval_lock(player, zone, Chzone_Lock)))) {
    if (noisy)
      notify(player, T("You cannot move that object to that zone."));
    return 0;
  }
  /* Don't chzone object to itself for mortals! */
  if ((zone == thing) && !Hasprivs(player)) {
    if (noisy)
      notify(player, T("You shouldn't zone objects to themselves!"));
    return 0;
  }
  /* Don't allow circular zones */
  if (GoodObject(zone)) {
    dbref tmp;
    int zone_depth = MAX_ZONES;
    for (tmp = Zone(zone); GoodObject(tmp); tmp = Zone(tmp)) {
      if (tmp == thing) {
	notify(player, T("You can't make circular zones!"));
	return 0;
      }
      if (tmp == Zone(tmp))	/* Ran into an object zoned to itself */
	break;
      zone_depth--;
      if (!zone_depth) {
	notify(player, T("Overly deep zone chain."));
	return 0;
      }
    }
  }

  /* Don't allow chzone to objects without elocks! 
   * If no lock is set, set a default lock (warn if zmo are used for control)
   * This checks for many trivial elocks (canuse/1, where &canuse=1)
   */
  if (zone != NOTHING)
    check_zone_lock(player, zone, noisy);

  /* Warn Wiz/Royals when they zone their stuff */
  if ((zone != NOTHING) && Hasprivs(Owner(thing))) {
    if (noisy)
      notify(player, T("Warning: @chzoning admin-owned object!"));
  }
  /* everything is okay, do the change */
  Zone(thing) = zone;
  /* If we're not unzoning, and we're working with a non-player object,
   * we'll remove wizard, royalty, inherit, and powers, for security.
   */
  if ((zone != NOTHING) && !IsPlayer(thing)) {
    /* if the object is a player, resetting these flags is rather
     * inconvenient -- although this may pose a bit of a security
     * risk. Be careful when @chzone'ing wizard or royal players.
     */
    clear_flag_internal(thing, "WIZARD");
    clear_flag_internal(thing, "ROYALTY");
    clear_flag_internal(thing, "TRUST");
    destroy_flag_bitmask(Powers(thing));
    Powers(thing) = new_flag_bitmask("POWER");
  } else {
    if (noisy && (zone != NOTHING)) {
      if (Hasprivs(thing))
	notify(player, T("Warning: @chzoning a privileged player."));
      if (Inherit(thing))
	notify(player, T("Warning: @chzoning an TRUST player."));
    }
  }
  if (noisy)
    notify(player, T("Zone changed."));
  return 1;
}

/** Structure for af_helper() data. */
struct af_args {
  int f;		/**< flag bits */
  int clear;		/**< True to remove the flag */
  char *flag;		/**< flag name */
};

static int
af_helper(dbref player, dbref thing,
	  dbref parent __attribute__ ((__unused__)),
	  char const *pattern
	  __attribute__ ((__unused__)), ATTR *atr, void *args)
{
  struct af_args *af = args;

  /* We must be able to write to that attribute normally,
   * to prevent players from doing funky things to, say, LAST.
   * There is one special case - the resetting of the SAFE flag.
   */
  if (!(Can_Write_Attr(player, thing, AL_ATTR(atr)) ||
	(af->clear && (af->f & AF_SAFE) &&
	 Can_Write_Attr_Ignore_Safe(player, thing, AL_ATTR(atr))))) {
    notify_format(player, T("You cannot change that flag on %s/%s"),
		  Name(thing), AL_NAME(atr));
    return 0;
  }

  if (af->clear) {
    AL_FLAGS(atr) &= ~af->f;
    if (!AreQuiet(player, thing))
      notify_format(player, T("%s/%s - %s reset."), Name(thing), AL_NAME(atr),
		    af->flag);
  } else {
    AL_FLAGS(atr) |= af->f;
    if (!AreQuiet(player, thing))
      notify_format(player, T("%s/%s - %s set."), Name(thing), AL_NAME(atr),
		    af->flag);
  }

  return 1;
}

static void
copy_attrib_flags(dbref player, dbref target, ATTR *atr, int flags)
{
  if (!atr)
    return;
  if (!Can_Write_Attr(player, target, AL_ATTR(atr))) {
    notify_format(player,
		  T("You cannot set attrib flags on %s/%s"), Name(target),
		  AL_NAME(atr));
    return;
  }
  AL_FLAGS(atr) = flags;
}

/** Set a flag on an attribute.
 * \param player the enactor.
 * \param obj the name of the object with the attribute.
 * \param atrname the name of the attribute.
 * \param flag the name of the flag to set or clear.
 */
void
do_attrib_flags(dbref player, const char *obj, const char *atrname,
		const char *flag)
{
  struct af_args af;
  dbref thing;
  const char *p;

  if ((thing = match_controlled(player, obj)) == NOTHING)
    return;

  if (!flag || !*flag) {
    notify(player, T("What flag do you want to set?"));
    return;
  }

  af.clear = 0;

  /* move past NOT token if there is one */
  for (p = flag; *p && ((*p == NOT_TOKEN) || isspace((unsigned char) *p)); p++)
    if (*p == NOT_TOKEN)
      af.clear = !af.clear;

  if ((af.f = string_to_atrflag(player, p)) < 0) {
    notify(player, T("Unrecognized attribute flag."));
    return;
  }
  af.flag = mush_strdup(atrflag_to_string(af.f), "af_flag list");
  if (!atr_iter_get(player, thing, atrname, 0, af_helper, &af))
    notify(player, T("No attribute found to change."));
  mush_free((Malloc_t) af.flag, "af_flag list");
}


/** Set a flag, attribute flag, or attribute.
 * \verbatim
 * This implements @set.
 * \endverbatim
 * \param player the enactor.
 * \param name the first (left) argument to the command.
 * \param flag the second (right) argument to the command.
 * \retval 1 successful set.
 * \retval 0 failure to set.
 */
int
do_set(dbref player, const char *name, char *flag)
{
  dbref thing;
  int her, listener, negate;
  char *p, *f;
  char flagbuff[BUFFER_LEN];

  /* check for attribute flag set first */
  if ((p = strchr(name, '/')) != NULL) {
    *p++ = '\0';
    do_attrib_flags(player, name, p, flag);
    return 1;
  }
  /* find thing */
  if ((thing = match_controlled(player, name)) == NOTHING)
    return 0;
  if (God(thing) && !God(player)) {
    notify(player, T("Only God can set himself!"));
    return 0;
  }
  /* check for attribute set first */
  if ((p = strchr(flag, ':')) != NULL) {
    *p++ = '\0';
    if (!command_check_byname(player, "ATTRIB_SET")) {
      notify(player, T("You may not set attributes."));
      return 0;
    }
    return do_set_atr(thing, flag, p, player, 1);
  }
  /* we haven't set an attribute, so we must be setting flags */
  strcpy(flagbuff, flag);
  p = trim_space_sep(flagbuff, ' ');
  if (*p == '\0') {
    notify(player, T("You must specify a flag to set."));
    return 0;
  }
  do {
    her = Hearer(thing);	/* Must be in loop, can change! */
    listener = Listener(thing);	/* Must be in loop, can change! */
    f = split_token(&p, ' ');
    negate = 0;
    if (*f == NOT_TOKEN && *(f + 1)) {
      negate = 1;
      f++;
    }
    set_flag(player, thing, f, negate, her, listener);
  } while (p);
  return 1;
}

/** Copy or move an attribute.
 * \verbatim
 * This implements @cpattr and @mvattr.
 * the command is of the format:
 * @cpattr oldobj/oldattr = newobj1/newattr1, newobj2/newattr2, etc.
 * \endverbatim
 * \param player the enactor.
 * \param oldpair the obj/attribute pair to copy from.
 * \param newpair array of obj/attribute pairs to copy to.
 * \param move if 1, move rather than copy.
 * \param noflagcopy if 1, don't copy associated flags.
 */
void
do_cpattr(dbref player, char *oldpair, char **newpair, int move, int noflagcopy)
{
  dbref oldobj, newobj;
  char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN];
  int i;
  char *p, *q;
  ATTR *a;
  char *text;
  int copies = 0;

  /* must copy from something */
  if (!oldpair || !*oldpair) {
    notify(player, T("What do you want to copy from?"));
    return;
  }
  /* find the old object */
  strcpy(tbuf1, oldpair);
  p = strchr(tbuf1, '/');
  if (!p || !*p) {
    notify(player, T("What object do you want to copy the attribute from?"));
    return;
  }
  *p++ = '\0';
  oldobj = noisy_match_result(player, tbuf1, NOTYPE, MAT_EVERYTHING);
  if (!GoodObject(oldobj))
    return;

  strcpy(tbuf2, p);
  p = tbuf2;
  /* find the old attribute */
  a = atr_get_noparent(oldobj, strupper(p));
  if (!a) {
    notify(player, T("No such attribute to copy from."));
    return;
  }
  /* check permissions to get it */
  if (!Can_Read_Attr(player, oldobj, a)) {
    notify(player, T("Permission to read attribute denied."));
    return;
  }
  /* we can read it. Copy the value. */
  text = safe_atr_value(a);

  /* now we loop through our new object pairs and copy, calling @set. */
  for (i = 1; i < MAX_ARG && (newpair[i] != NULL); i++) {
    if (!*newpair[i]) {
      notify(player, T("What do you want to copy to?"));
    } else {
      strcpy(tbuf1, newpair[i]);
      q = strchr(tbuf1, '/');
      if (!q || !*q) {
	q = (char *) AL_NAME(a);
      } else {
	*q++ = '\0';
      }
      newobj = noisy_match_result(player, tbuf1, NOTYPE, MAT_EVERYTHING);
      if (GoodObject(newobj) &&
	  ((newobj != oldobj) || strcasecmp(AL_NAME(a), q)) &&
	  do_set_atr(newobj, q, text, player, 1))
	copies++;
      /* copy the attribute flags too */
      if (!noflagcopy)
	copy_attrib_flags(player, newobj,
			  atr_get_noparent(newobj, strupper(q)), a->flags);

    }
  }

  free((Malloc_t) text);	/* safe_uncompress malloc()s memory */
  if (copies) {
    notify_format(player, T("Attribute %s (%d copies)"),
		  (move ? "moved" : "copied"), copies);
    if (move)
      do_set_atr(oldobj, AL_NAME(a), NULL, player, 1);
  } else {
    notify_format(player, T("Unable to %s attribute."),
		  (move ? "move" : "copy"));
  }
  return;
}

/** Argument struct for gedit_helper */
struct gedit_args {
  enum edit_type target; /**< The type of edit */
  char *from; /**< What is going to be replaced? */
  char *to; /**< What it gets replaced with. */
};

static int
gedit_helper(dbref player, dbref thing,
	     dbref parent __attribute__ ((__unused__)),
	     char const *pattern
	     __attribute__ ((__unused__)), ATTR *a, void *args)
{
  int ansi_long_flag = 0;
  const char *r;
  char *s, *val;
  char tbuf1[BUFFER_LEN], tbuf_ansi[BUFFER_LEN];
  char *tbufp, *tbufap;
  size_t rlen, vlen;
  struct gedit_args *gargs;

  gargs = args;

  val = gargs->from;
  vlen = strlen(val);
  r = gargs->to ? gargs->to : "";
  rlen = strlen(r);

  tbufp = tbuf1;
  tbufap = tbuf_ansi;

  if (!a) {			/* Shouldn't ever happen, but better safe than sorry */
    notify(player, T("No such attribute, try set instead."));
    return 0;
  }
  if (!Can_Write_Attr(player, thing, a)) {
    notify(player, T("You need to control an attribute to edit it."));
    return 0;
  }
  s = (char *) atr_value(a);	/* warning: pointer to static buffer */

  if (vlen == 1 && *val == '$') {
    /* append */
    safe_str(s, tbuf1, &tbufp);
    safe_str(r, tbuf1, &tbufp);

    if (safe_format(tbuf_ansi, &tbufap, "%s%s%s%s", s, ANSI_HILITE, r,
		    ANSI_NORMAL))
      ansi_long_flag = 1;
  } else if (vlen == 1 && *val == '^') {
    /* prepend */
    safe_str(r, tbuf1, &tbufp);
    safe_str(s, tbuf1, &tbufp);

    if (safe_format(tbuf_ansi, &tbufap, "%s%s%s%s", ANSI_HILITE, r, ANSI_NORMAL,
		    s))
      ansi_long_flag = 1;
  } else if (!*val) {
    /* insert replacement string between every character */
    ansi_string *haystack;
    size_t last = 0;
    int too_long = 0;

    haystack = parse_ansi_string(s);

    /* Add one at the start */
    if (!safe_strl(r, rlen, tbuf1, &tbufp)) {
      if (gargs->target != EDIT_FIRST) {
	for (last = 0; last < haystack->len; last++) {
	  /* Add the next character */
	  if (safe_ansi_string(haystack, last, 1, tbuf1, &tbufp)) {
	    too_long = 1;
	    break;
	  }
	  if (!ansi_long_flag) {
	    if (safe_ansi_string(haystack, last, 1, tbuf_ansi, &tbufap))
	      ansi_long_flag = 1;
	  }
	  /* Copy in r */
	  if (safe_strl(r, rlen, tbuf1, &tbufp)) {
	    too_long = 1;
	    break;
	  }
	  if (!ansi_long_flag) {
	    if (safe_format(tbuf_ansi, &tbufap, "%s%s%s", ANSI_HILITE, r,
			    ANSI_NORMAL))
	      ansi_long_flag = 1;
	  }
	}
      }
    }
    free_ansi_string(haystack);
  } else {
    /* find and replace */
    ansi_string *haystack;
    size_t last = 0;
    char *p;
    int too_long = 0;

    haystack = parse_ansi_string(s);

    while (last < haystack->len
	   && (p = strstr(haystack->text + last, val)) != NULL) {
      if (safe_ansi_string(haystack, last, p - (haystack->text + last),
			   tbuf1, &tbufp)) {
	too_long = 1;
	break;
      }
      if (!ansi_long_flag) {
	if (safe_ansi_string(haystack, last, p - (haystack->text + last),
			     tbuf_ansi, &tbufap))
	  ansi_long_flag = 1;
      }

      /* Copy in r */
      if (safe_strl(r, rlen, tbuf1, &tbufp)) {
	too_long = 1;
	break;
      }
      if (!ansi_long_flag) {
	if (safe_format(tbuf_ansi, &tbufap, "%s%s%s", ANSI_HILITE, r,
			ANSI_NORMAL))
	  ansi_long_flag = 1;
      }
      last = p - haystack->text + vlen;
      if (gargs->target == EDIT_FIRST)
	break;
    }
    if (last < haystack->len && !too_long) {
      safe_ansi_string(haystack, last, haystack->len, tbuf1, &tbufp);
      if (!ansi_long_flag) {
	if (safe_ansi_string(haystack, last, haystack->len, tbuf_ansi, &tbufap))
	  ansi_long_flag = 1;
      }
    }
    free_ansi_string(haystack);
  }

  *tbufp = '\0';
  *tbufap = '\0';

  if (do_set_atr(thing, AL_NAME(a), tbuf1, player, 0) &&
      !AreQuiet(player, thing)) {
    if (!ansi_long_flag && ShowAnsi(player))
      notify_format(player, "%s - Set: %s", AL_NAME(a), tbuf_ansi);
    else
      notify_format(player, "%s - Set: %s", AL_NAME(a), tbuf1);
  }

  return 1;
}

/** Edit an attribute.
 * \verbatim
 * This implements @edit obj/attribute = {search}, {replace}
 * \endverbatim
 * \param player the enactor.
 * \param it the object/attribute pair.
 * \param argv array containing the search and replace strings.
 */
void
do_gedit(dbref player, char *it, char **argv, enum edit_type target)
{
  dbref thing;
  char tbuf1[BUFFER_LEN];
  char *q;
  struct gedit_args args;

  if (!(it && *it)) {
    notify(player, T("I need to know what you want to edit."));
    return;
  }
  strcpy(tbuf1, it);
  q = strchr(tbuf1, '/');
  if (!(q && *q)) {
    notify(player, T("I need to know what you want to edit."));
    return;
  }
  *q++ = '\0';
  thing = noisy_match_result(player, tbuf1, NOTYPE, MAT_EVERYTHING);

  if ((thing == NOTHING) || !controls(player, thing)) {
    notify(player, T("Permission denied."));
    return;
  }

  if (!argv[1] || !*argv[1]) {
    notify(player, T("Nothing to do."));
    return;
  }
  args.from = argv[1];
  args.to = argv[2];
  args.target = target;
  if (!atr_iter_get(player, thing, q, 0, gedit_helper, &args))
    notify(player, T("No matching attributes."));
}

/** Trigger an attribute.
 * \verbatim
 * This implements @trigger obj/attribute = list-of-arguments.
 * \endverbatim
 * \param player the enactor.
 * \param object the object/attribute pair.
 * \param argv array of arguments.
 */
void
do_trigger(dbref player, char *object, char **argv)
{
  dbref thing;
  int a;
  char *s;
  char tbuf1[BUFFER_LEN];

  strcpy(tbuf1, object);
  for (s = tbuf1; *s && (*s != '/'); s++) ;
  if (!*s) {
    notify(player, T("I need to know what attribute to trigger."));
    return;
  }
  *s++ = '\0';

  thing = noisy_match_result(player, tbuf1, NOTYPE, MAT_EVERYTHING);

  if (thing == NOTHING)
    return;

  if (!controls(player, thing) && !(Owns(player, thing) && LinkOk(thing))) {
    notify(player, T("Permission denied."));
    return;
  }
  if (God(thing) && !God(player)) {
    notify(player, T("You can't trigger God!"));
    return;
  }
  /* trigger modifies the stack */
  for (a = 0; a < 10; a++)
    global_eval_context.wnxt[a] = argv[a + 1];

  if (charge_action(player, thing, upcasestr(s))) {
    if (!AreQuiet(player, thing))
      notify_format(player, "%s - Triggered.", Name(thing));
  } else {
    notify(player, T("No such attribute."));
  }
}


/** The use command.
 * It's here for lack of a better place.
 * \param player the enactor.
 * \param what name of the object to use.
 */
void
do_use(dbref player, const char *what)
{
  dbref thing;

  /* if we pass the use key, do it */

  if ((thing =
       noisy_match_result(player, what, TYPE_THING,
			  MAT_NEAR_THINGS)) != NOTHING) {
    if (!eval_lock(player, thing, Use_Lock)) {
      fail_lock(player, thing, Use_Lock, T("Permission denied."), NOTHING);
      return;
    } else
      did_it(player, thing, "USE", "Used.", "OUSE", NULL, "AUSE", NOTHING);
  }
}

/** Parent an object to another.
 * \verbatim
 * This implements @parent.
 * \endverbatim
 * \param player the enactor.
 * \param name the name of the child object.
 * \param parent_name the name of the new parent object.
 */
void
do_parent(dbref player, char *name, char *parent_name)
{
  dbref thing;
  dbref parent;
  dbref check;
  int i;

  if ((thing = noisy_match_result(player, name, NOTYPE, MAT_NEARBY)) == NOTHING)
    return;

  if (!parent_name || !*parent_name || !strcasecmp(parent_name, "none"))
    parent = NOTHING;
  else if ((parent = noisy_match_result(player, parent_name, NOTYPE,
					MAT_EVERYTHING)) == NOTHING)
    return;

  /* do control check */
  if (!controls(player, thing) && !(Owns(player, thing) && LinkOk(thing))) {
    notify(player, T("Permission denied."));
    return;
  }
  /* a player may change an object's parent to NOTHING or to an 
   * object he owns, or one that is LINK_OK when the player passes
   * the parent lock
   * mod: also when the player controls the parent, it passes the parent lock
   * [removed owner and wizard check and added
   * control check (wich does those things
   * anyway, right?)]
   */
  if ((parent != NOTHING) && !controls(player, parent) &&
      !(LinkOk(parent) && eval_lock(player, parent, Parent_Lock))) {
    notify(player, T("Permission denied."));
    return;
  }
  /* check to make sure no recursion can happen */
  if (parent == thing) {
    notify(player, T("A thing cannot be its own ancestor!"));
    return;
  }
  if (parent != NOTHING) {
    for (i = 0, check = Parent(parent);
	 (i < MAX_PARENTS) && (check != NOTHING); i++, check = Parent(check)) {
      if (check == thing) {
	notify(player, T("You are not allowed to be your own ancestor!"));
	return;
      }
    }
    if (i >= MAX_PARENTS) {
      notify(player, T("Too many ancestors."));
      return;
    }
  }
  /* everything is okay, do the change */
  Parent(thing) = parent;
  if (!AreQuiet(player, thing))
    notify(player, T("Parent changed."));
}

static int
wipe_helper(dbref player, dbref thing,
	    dbref parent __attribute__ ((__unused__)),
	    char const *pattern,
	    ATTR *atr, void *args __attribute__ ((__unused__)))
{
  /* for added security, only God can modify wiz-only-modifiable
   * attributes using this command and wildcards.  Wiping a specific
   * attr still works, though.
   */
  if (wildcard(pattern) && AF_Wizard(atr) && !God(player))
    return 0;
  return do_set_atr(thing, AL_NAME(atr), NULL, player, 0) == 1;
}

/** Clear an attribute.
 * \verbatim
 * This implements @wipe.
 * \endverbatim
 * \param player the enactor.
 * \param name the object/attribute-pattern to wipe.
 */
void
do_wipe(dbref player, char *name)
{
  dbref thing;
  char *pattern;

  if ((pattern = strchr(name, '/')) != NULL)
    *pattern++ = '\0';

  if ((thing = noisy_match_result(player, name, NOTYPE, MAT_NEARBY)) == NOTHING)
    return;

  /* this is too destructive of a command to be used by someone who
   * doesn't own the object. Thus, the check is on Owns not controls.
   */
  if (!Wizard(player) && !Owns(player, thing)) {
    notify(player, T("Permission denied."));
    return;
  }
  /* protect SAFE objects unless doing a non-wildcard pattern */
  if (Safe(thing) && !(pattern && *pattern && !wildcard(pattern))) {
    notify(player, T("That object is protected."));
    return;
  }

  we_are_wiping = 1;

  if (!atr_iter_get(player, thing, pattern, 0, wipe_helper, NULL))
    notify(player, T("No attributes wiped."));
  else
    notify(player, T("Attributes wiped."));

  we_are_wiping = 0;
}