/* $Header: /belch_a/users/rearl/tinymuck/src/RCS/set.c,v 1.13 90/09/18 08:02:06 rearl Exp $ */

/*
 * $Log:	set.c,v $
 * Revision 1.13  90/09/18  08:02:06  rearl
 * Player hash table mods, took out FILTER.
 * 
 * Revision 1.12  90/09/16  04:42:56  rearl
 * Preparation code added for disk-based MUCK.
 * 
 * Revision 1.11  90/09/10  02:23:11  rearl
 * Took out get_string call.
 * 
 * Revision 1.10  90/08/27  03:33:18  rearl
 * Fixed bug involving NULL player password entries.
 * 
 * Revision 1.9  90/08/11  04:10:15  rearl
 * *** empty log message ***
 * 
 * Revision 1.8  90/08/06  03:51:35  rearl
 * Redid @chown thing = me, the easy way...
 * 
 * Revision 1.7  90/08/06  02:51:18  rearl
 * Added restricted() routine from predicates.c, any restricted
 * flags should be checked in there from now on.
 * 
 * Revision 1.6  90/08/05  03:20:01  rearl
 * Redid matching routines.
 * 
 * Revision 1.5  90/08/02  18:51:26  rearl
 * Enabled @chown <thing> = me in addition to @chown <thing>.
 * 
 * Revision 1.4  90/07/29  17:43:30  rearl
 * Allows players to capitalize/decapitalize their names, added support
 * for setting programs DEBUG (DARK).
 * 
 * 
 * Revision 1.3  90/07/21  16:31:50  casie
 * added log_misc
 * 
 * Revision 1.2  90/07/21  05:32:41  casie
 * Changed player chown to allow rooms and exits as well as things.
 * 
 * Revision 1.1  90/07/19  23:04:09  casie
 * Initial revision
 * 
 *
 */

#include "copyright.h"
#include "config.h"

/* commands which set parameters */
#include <stdio.h>
#include <ctype.h>
#include "strings.h"

#include "db.h"
#include "params.h"
#include "match.h"
#include "interface.h"
#include "externs.h"

dbref match_controlled(dbref player, const char *name)
{
  dbref match;
  struct match_data md;
  
  init_match(player, name, NOTYPE, &md);
  match_everything(&md);

  match = noisy_match_result(&md);
  if(match != NOTHING && !(controls(player, match) || player == OWNER(match))
     && !God(player)) {
    notify(player, "Permission denied.");
    return NOTHING;
  } else {
    return match;
  }
}

void do_name(dbref player, const char *name, char *newname)
{
  dbref thing;
  char *password;
  
  if((thing = match_controlled(player, name)) != NOTHING) {
    /* check for bad name */
    if(*newname == '\0') {
      notify(player, "Give it what new name?");
      return;
    }

    /* check for renaming a player */
    if(Typeof(thing) == TYPE_PLAYER) {
      /* split off password */
      for(password = newname;
	  *password && !isspace(*password);
	  password++);
      /* eat whitespace */
      if(*password) {
	*password++ = '\0'; /* terminate name */
	while(*password && isspace(*password)) password++;
      }
      
      /* check for null password */
      if(!*password) {
	notify(player,
	       "You must specify a password to change a player name.");
	notify(player, "E.g.: name player = newname password");
	return;
      } else if(strcmp(password, DoNull(DBFETCH(thing)->sp.player.password))) {
	notify(player, "Incorrect password.");
	return;
      } else if(string_compare(newname, NAME(thing))
		&& !ok_player_name(newname)) {
	notify(player, "You can't give a player that name.");
	return;
      }
      /* everything ok, notify */
      log_status("NAME CHANGE: %s(#%d) to %s\n",
		 NAME(thing), thing, newname);
      delete_player(thing);
      if (NAME(thing)) free((void *) NAME(thing));
      NAME(thing) = alloc_string(newname);
      add_player(thing);
      notify(player, "Name set.");
      return;
    } else {
      if(!ok_name(newname)) {
	notify(player, "That is not a reasonable name.");
	return;
      }
    }
    
    /* everything ok, change the name */
    if(NAME(thing)) {
      free((void *) NAME(thing));
    }
    NAME(thing) = alloc_string(newname);
    notify(player, "Name set.");
    DBDIRTY(thing);
  }
}

void do_attr(dbref player, const char *name, const char *what,
	      const char *value)
{
  dbref thing;

  if((thing = match_controlled(player, what)) != NOTHING) {
    char *attribute = (char *) calloc(strlen(name) + 2, sizeof(char));
    *attribute = PROP_WIZARD;
    strcat(attribute, name);
    if(value && *value) {
      add_property(thing, attribute, value);
      sprintf(buf,"%s -- %c%s set to \"%s\"", NAME(thing), PROP_WIZARD, name,
	      value);
      notify(player, buf);
    } else {
      remove_property(thing, attribute);
      sprintf(buf,"%s -- %c%s removed.", NAME(thing), PROP_WIZARD, name);
      notify(player, buf);
    }
    free((void *) attribute);
  }
}

void do_lock(dbref player, const char *name, const char *keyname)
{
  dbref thing;
  struct boolexp *key;
  struct match_data md;
  
  init_match(player, name, NOTYPE, &md);
  match_everything(&md);

  switch(thing = match_result(&md)) {
  case NOTHING:
    notify(player, "I don't see what you want to lock!");
    return;
  case AMBIGUOUS:
    notify(player, "I don't know which one you want to lock!");
    return;
  default:
    if(!controls(player, thing)) {
      notify(player, "You can't lock that!");
      return;
    }
    break;
  }
  
  key = parse_boolexp(player, keyname);
  if(key == TRUE_BOOLEXP) {
    notify(player, "I don't understand that key.");
  } else {
    /* everything ok, do it */
    free_boolexp(DBFETCH(thing)->key);
    DBSTORE(thing, key, key);
    notify(player, "Locked.");
  }
}

void do_unlock(dbref player, const char *name)
{
  dbref thing;
  
  if((thing = match_controlled(player, name)) != NOTHING) {
    free_boolexp(DBFETCH(thing)->key);
    DBSTORE(thing, key, TRUE_BOOLEXP);
    notify(player, "Unlocked.");
  }
}

void do_unlink(dbref player, const char *name)
{
  dbref exit;
  struct match_data md;
  
  init_match(player, name, TYPE_EXIT, &md);
  match_all_exits(&md);
  match_here(&md);
  match_absolute(&md);
  
  switch(exit = match_result(&md)) {
  case NOTHING:
    notify(player, "Unlink what?");
    break;
  case AMBIGUOUS:
    notify(player, "I don't know which one you mean!");
    break;
  default:
    if(!controls(player, exit)) {
      notify(player, "Permission denied.");
    } else {
      switch(Typeof(exit)) {
      case TYPE_EXIT:
        if(DBFETCH(exit)->sp.exit.ndest != 0) {
	    DBFETCH(OWNER(exit))->sp.player.pennies += LINK_COST;
	    DBDIRTY(OWNER(exit));
	}
	DBSTORE(exit, sp.exit.ndest, 0);
	if (DBFETCH(exit)->sp.exit.dest) {
	  free ((void *) DBFETCH(exit)->sp.exit.dest);
	  DBSTORE(exit, sp.exit.dest, NULL);
	}
	notify(player, "Unlinked.");
	break;
      case TYPE_ROOM:
	DBSTORE(exit, link, NOTHING);
	notify(player, "Dropto removed.");
	break;
      default:
	notify(player, "You can't unlink that!");
	break;
      }
    }
  }
}

#ifdef PLAYER_CHOWN
void do_chown(dbref player, const char *name, const char *newowner)
{
  dbref thing;
  dbref owner;
  struct match_data md;
  
  if (!*name) {
    notify(player, "You must specify what you want to take ownership of.");
    return;
  }
  
  init_match(player, name, NOTYPE, &md);
  match_everything(&md);
  if((thing = noisy_match_result(&md)) == NOTHING) return;
  
  if(*newowner && string_compare(newowner, "me")) {
    if ((owner = lookup_player(newowner)) == NOTHING) {
      notify(player, "I couldn't find that player.");
      return;
    }
  }
  else {
    owner = player;
  }
  
  if(!Wizard(player) && player != owner) {
    notify(player, "Only wizards can transfer ownership to others.");
    return;
  }
  
  if (!Wizard(player) && (!(FLAGS(thing) & CHOWN_OK)
			  || Typeof(thing) == TYPE_PROGRAM)) {
    notify(player, "You can't take possession of that.");
    return;
  }

  switch (Typeof(thing)) {
  case TYPE_ROOM:
    if (!Arch(player) && DBFETCH(player)->location != thing) {
      notify(player, "You can only chown \"here\".");
      return;
    }
    OWNER(thing) = owner;
    break;
  case TYPE_THING:
    if (!Arch(player) && DBFETCH(thing)->location != player) {
      notify(player, "You aren't carrying that.");
      return;
    }
    if (Typeof(player) == TYPE_PLAYER)
      OWNER(thing) = owner;
    FLAGS(thing) |= HALTED; /* halted... igg */
    halt_long(thing);
    break;
  case TYPE_PLAYER:
    if(!Wizard(player) || Wizard(thing)) {
      notify(player, "Players always own themselves.");
      return;
    }
    DBFETCH(thing)->owner = owner; /* gotta do it w/o OWNER, cause owner */
    break;			/* is messed up for players. */
  case TYPE_EXIT:
    OWNER(thing) = owner;
    break;
#ifdef RECYCLE
  case TYPE_GARBAGE:
    notify(player, "No one wants to own garbage.");
    return;
#endif	
  default:
   if(!Arch(player)) {
      notify(player,"Permission denied.");
      return;
    }
    OWNER(thing) = owner;
    break;
  }
  if (owner == player)
    notify(player, "Owner changed to you.");
  else {
    char buf[BUFFER_LEN];
    sprintf(buf, "Owner changed to %s.", unparse_object(player, owner));
    notify(player, buf);
  }
  DBDIRTY(thing);
}

#else    /* without PLAYER_CHOWN */
void do_chown(dbref player, const char *name, const char *newowner)
{
  dbref thing;
  dbref owner;
  struct match_data md;
  
  if(!Arch(player)) {
    notify(player, "Permission denied.");
    return;
  }
  
  init_match(player, name, NOTYPE, &md);
  match_everything(&md);
  if((thing = noisy_match_result(&md)) == NOTHING) return;
  if((owner = lookup_player(newowner)) == NOTHING) {
    notify(player, "I couldn't find that player.");
    return;
  }
  switch (Typeof(thing)) {
  case TYPE_PLAYER:
    notify(player, "Players always own themselves.");
    return;
  case TYPE_ROOM:
  case TYPE_THING:
  case TYPE_EXIT:
  case TYPE_PROGRAM:
    OWNER(thing) = owner;
#ifdef RECYCLE
  case TYPE_GARBAGE:
    notify(player, "No one wants to own garbage.");
    return;
#endif	
  }
  notify(player, "Owner changed.");
  DBDIRTY(thing);
}

#endif /* PLAYER_CHOWN */

/* Note: Gender code taken out.  All gender references are now to be handled
   by property lists...
   Setting of flags and property code done here.  Note that the PROP_DELIMITER
   identifies when you're setting a property.
   A @set <thing>= :
   will clear all properties.
   A @set <thing>= type:
   will remove that property.
   A @set <thing>= type: class
   will add that property or replace it.                                    */

void do_set(dbref player, const char *name, const char *flag)
{
  dbref thing;
  const char *p;
  object_flag_type f;
  int did_hear;			/* hmm.... whether it heard before we
				 * did a set on it. */
  
  /* find thing */
  if((thing = match_controlled(player, name)) == NOTHING) return;
  
  /* move p past NOT_TOKEN if present */
  for(p = flag; *p && (*p == NOT_TOKEN || isspace(*p)); p++);
  
  /* Now we check to see if it's a property reference */
  /* if this gets changed, please also modify boolexp.c */
  if (index(flag, PROP_DELIMITER))
    {
      /* copy the string so we can muck with it */
      char    *type = alloc_string(flag);   /* type */
      char    *class = (char *)index(type, PROP_DELIMITER);  /* class */
      char    *x; /* to preserve string location so we can free it */
      char    *temp;
      struct plist *prop, *last;

      x = type;
      while (isspace(*type) && (*type != PROP_DELIMITER))
	type++;
      if ((*type == PROP_DELIMITER) || ((*type == PROP_WIZARD)
					&& (*(type + 1) == PROP_DELIMITER)))
	{
	  did_hear = can_hear(thing);
	  if(*type == PROP_DELIMITER) {
	    prop = DBFETCH(thing)->properties;
	  } else {
	    prop = DBFETCH(thing)->attributes;
	  }
	  /* clear all properties */
	  if(prop) {
	    for (last = prop -> next; last; last = prop -> next)
	      {
		prop -> next = last -> next;
		free_prop(last);
	      }
	    if(*type == PROP_DELIMITER) {
	      notify(player, "All properties removed.");
	      DBSTORE(thing, properties, NULL);
	    } else {
	      notify(player, "All attributes removed.");
	      DBSTORE(thing, attributes, NULL);
	    }
  	    free_prop(prop);
	  } else {   /* No properties to remove. */
	    if(*type == PROP_DELIMITER)
	      notify(player, "No properties to remove.");
	    else
	      notify(player, "No attributes to remove.");
	  }

	  free((void *) x);

	  if(can_hear(thing) && !did_hear)
	    grow_ears(thing);
	  if(!can_hear(thing) && did_hear)
	    lose_ears(thing);
	  return;
	}
      else
	{
	  /* get rid of trailing spaces */
	  for (temp = class - 1; isspace(*temp); temp--)
	    ;
	  temp++;
	  *temp = '\0';
	}
      class++; /* move to next character */
      while (isspace(*class) && *class)
	class++;
      if (!(*class))
	{
	  remove_property(thing, type);
	  sprintf(buf, "%s -- %s removed.", NAME(thing), type);
	  notify(player, buf);
	}
      else
	{
	  if(*type == PROP_WIZARD && !Wizard(player))
	    {
	      notify(player, "Permission denied.");
	      free ((void *) x);
	      return;
	    }
	  add_property(thing, type, class);
	  sprintf(buf, "%s -- %s set to \"%s\"", NAME(thing), type, class);
	  notify(player, buf);
	}
      free((void *) x);
      return;
    }
  did_hear = can_hear(thing);
  
  /* identify flag */

  f = lookup_flag(p);
  if(f <= 0) {
    notify(player,"I don't recognize that flag.");
    return;
  }

  /* check for restricted flag */
  if (restricted(player, thing, f)) {
    notify(player, "Permission denied.");
    return;
  }
  
  /* check for stupid wizard */
  if(f == WIZARD && *flag == NOT_TOKEN && thing == player && !God(player)) {
    notify(player, "You cannot make yourself mortal.");
    return;
  }
  /* else everything is ok, do the set */
  if(*flag == NOT_TOKEN) {
    /* reset the flag */
    FLAGS(thing) &= ~f;
    DBDIRTY(thing);
    notify(player, "Flag reset.");
  } else {
    /* set the flag */
    FLAGS(thing) |= f;
    DBDIRTY(thing);
    notify(player, "Flag set.");
  }
  if(can_hear(thing) && !did_hear)
    grow_ears(thing);
  if(!can_hear(thing) && did_hear)
    lose_ears(thing);
}

void do_field(dbref player, const char *name, const char *flag, dbref cause)
{
  dbref thing;
  const char *p;

  /* find thing */
  if((thing = match_controlled(player, name)) == NOTHING) return;

  /* move p past NOT_TOKEN if present */
  for(p = flag; *p && (*p == NOT_TOKEN || isspace(*p)); p++);

  /* Now we check to see if it's a property reference */
  /* if this gets changed, please also modify boolexp.c */
  if (index(flag, PROP_DELIMITER))
    {
      /* copy the string so we can muck with it */
      char    *type = alloc_string(flag);   /* type */
      char    *rest  = (char *)index(type, PROP_DELIMITER);  /* class */
      char    *x; /* to preserve string location so we can free it */
      char    *temp;
      char    *old;

      char new[BUFFER_LEN];
      x = type;

      while (isspace(*type) && (*type != PROP_DELIMITER))
        type++;
      if (*type == PROP_DELIMITER)
        {
	  notify(player, "Usage:  @field <object>=<property>:<old>,<new>");
          free((void *) x);
	  return;
	}
      else
        {
          /* get rid of trailing spaces */
          for (temp = rest - 1; isspace(*temp); temp--)
            ;
          temp++;
	  *temp = '\0';
	}

      if(get_property_class(thing, type)) {
	old = (char *)
	  calloc(strlen(get_property_class(thing, type)) + 1, sizeof(char));
	strcpy(old, get_property_class(thing, type));
      }
      else {
	notify(player, "No such property.");
	free((void *) x);
	return;
      }
      
      rest++; /* move to next character */
      while (isspace(*rest) && *rest)
	rest++;
      if (!(*rest))
	{
	  notify(player, "Nothing to change.");
	  free((void *) x);
	  return;
	}
      else
	{
	  const char *delimit = ",";

	  char *arg1 = (char *) parse_up(&rest, delimit);
          char *arg2 = (char *) parse_up(&rest, delimit);

	  arg1 = check_arg(arg1, player, cause);
	  arg2 = check_arg(arg2, player, cause);

	  if(!arg1 || !(*arg1)) {
	    notify(player, "Nothing to change.");
	    free((void *) x);
	    return;
	  }
	  else {
	    int len = strlen(arg1);
	    int d;
	    const char *r = (arg2 ? arg2 : (char *) "");
    
	    for (d = 0; (d < BUFFER_LEN) && *old;)
	      if (strncmp(arg1, old, len) == 0) {
		if ((d + strlen(r)) < BUFFER_LEN) {
		  strcpy(new + d, r);
		  d += strlen(r);
		  old += len;
		} else
		  new[d++] = *old++;
	      } else
		new[d++] = *old++;
	    new[d++] = 0;

	    free((void *) old);

	    if(new && *new) {
	      add_property(thing, type, new);
	      sprintf(buf,"%s -- %s set to \"%s\"", NAME(thing), type, new);
	    }
	    else {
	      remove_property(thing, type);
	      sprintf(buf,"%s -- %s removed.", NAME(thing), type);
	    }
	    notify(player, buf);
	  }
	}
      free((void *) x);
    }
  else
    notify(player, "Usage:  @field <object>=<old>,<new>");
}